fix: Delete ai comments

This commit is contained in:
2025-11-06 00:58:10 +03:00
parent 5298fb9f60
commit 1917b2cf5a
41 changed files with 203 additions and 1107 deletions

View File

@@ -47,10 +47,8 @@ export function InfiniteCanvas({
const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
const [isPointerDown, setIsPointerDown] = useState(false);
// Флаг для предотвращения конфликта между пользовательским вводом и данными маршрута
const [isUserInteracting, setIsUserInteracting] = useState(false);
// Реф для отслеживания последнего значения originalRouteData?.rotate
const lastOriginalRotation = useRef<number | undefined>(undefined);
useEffect(() => {
@@ -68,7 +66,7 @@ export function InfiniteCanvas({
const handlePointerDown = (e: FederatedMouseEvent) => {
setIsPointerDown(true);
setIsDragging(false);
setIsUserInteracting(true); // Устанавливаем флаг взаимодействия пользователя
setIsUserInteracting(true);
setStartPosition({
x: position.x,
y: position.y,
@@ -81,13 +79,9 @@ export function InfiniteCanvas({
e.stopPropagation();
};
// Устанавливаем rotation только при изменении originalRouteData и отсутствии взаимодействия пользователя
useEffect(() => {
const newRotation = originalRouteData?.rotate ?? 0;
// Обновляем rotation только если:
// 1. Пользователь не взаимодействует с канвасом
// 2. Значение действительно изменилось
if (!isUserInteracting && lastOriginalRotation.current !== newRotation) {
setRotation((newRotation * Math.PI) / 180);
lastOriginalRotation.current = newRotation;
@@ -97,7 +91,6 @@ export function InfiniteCanvas({
const handlePointerMove = (e: FederatedMouseEvent) => {
if (!isPointerDown) return;
// Проверяем, началось ли перетаскивание
if (!isDragging) {
const dx = e.globalX - startMousePosition.x;
const dy = e.globalY - startMousePosition.y;
@@ -119,10 +112,8 @@ export function InfiniteCanvas({
e.globalX - center.x
);
// Calculate rotation difference in radians
const rotationDiff = currentAngle - startAngle;
// Update rotation
setRotation(startRotation + rotationDiff);
const cosDelta = Math.cos(rotationDiff);
@@ -149,15 +140,13 @@ export function InfiniteCanvas({
};
const handlePointerUp = (e: FederatedMouseEvent) => {
// Если не было перетаскивания, то это простой клик - закрываем виджет
if (!isDragging) {
setSelectedSight(undefined);
}
setIsPointerDown(false);
setIsDragging(false);
// Сбрасываем флаг взаимодействия через небольшую задержку
// чтобы избежать немедленного срабатывания useEffect
setTimeout(() => {
setIsUserInteracting(false);
}, 100);
@@ -166,29 +155,25 @@ export function InfiniteCanvas({
const handleWheel = (e: FederatedWheelEvent) => {
e.stopPropagation();
setIsUserInteracting(true); // Устанавливаем флаг при зуме
setIsUserInteracting(true);
// Get mouse position relative to canvas
const mouseX = e.globalX - position.x;
const mouseY = e.globalY - position.y;
// Calculate new scale
const scaleMin = (routeData?.scale_min ?? 10) / SCALE_FACTOR;
const scaleMax = (routeData?.scale_max ?? 20) / SCALE_FACTOR;
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; // Zoom out/in
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
const newScale = Math.max(scaleMin, Math.min(scaleMax, scale * zoomFactor));
const actualZoomFactor = newScale / scale;
if (scale === newScale) {
// Сбрасываем флаг, если зум не изменился
setTimeout(() => {
setIsUserInteracting(false);
}, 100);
return;
}
// Update position to zoom towards mouse cursor
setPosition({
x: position.x + mouseX * (1 - actualZoomFactor),
y: position.y + mouseY * (1 - actualZoomFactor),
@@ -196,7 +181,6 @@ export function InfiniteCanvas({
setScale(newScale);
// Сбрасываем флаг взаимодействия через задержку
setTimeout(() => {
setIsUserInteracting(false);
}, 100);

View File

@@ -141,7 +141,6 @@ export const MapDataProvider = observer(
}, [routeId]);
useEffect(() => {
// combine changes with original data
if (originalRouteData)
setRouteData({ ...originalRouteData, ...routeChanges });
if (originalSightData) setSightData(originalSightData);

View File

@@ -37,11 +37,9 @@ export function RightSidebar() {
useEffect(() => {
if (originalRouteData) {
// Проверяем и сбрасываем минимальный масштаб если нужно
const originalMinScale = originalRouteData.scale_min ?? 1;
const resetMinScale = originalMinScale < 1 ? 1 : originalMinScale;
// Проверяем и сбрасываем максимальный масштаб если нужно
const originalMaxScale = originalRouteData.scale_max ?? 5;
const resetMaxScale = originalMaxScale < 3 ? 3 : originalMaxScale;
@@ -130,7 +128,6 @@ export function RightSidebar() {
onChange={(e) => {
let newMinScale = Number(e.target.value);
// Сбрасываем к 1 если меньше
if (newMinScale < 1) {
newMinScale = 1;
}
@@ -139,10 +136,10 @@ export function RightSidebar() {
if (maxScale - newMinScale < 2) {
let newMaxScale = newMinScale + 2;
// Сбрасываем максимальный к 3 если меньше минимального
if (newMaxScale < 3) {
newMaxScale = 3;
setMinScale(1); // Сбрасываем минимальный к 1
setMinScale(1);
}
setMaxScale(newMaxScale);
}
@@ -175,7 +172,6 @@ export function RightSidebar() {
onChange={(e) => {
let newMaxScale = Number(e.target.value);
// Сбрасываем к 3 если меньше минимального
if (newMaxScale < 3) {
newMaxScale = 3;
}
@@ -184,10 +180,10 @@ export function RightSidebar() {
if (newMaxScale - minScale < 2) {
let newMinScale = newMaxScale - 2;
// Сбрасываем минимальный к 1 если меньше
if (newMinScale < 1) {
newMinScale = 1;
setMaxScale(3); // Сбрасываем максимальный к минимальному значению
setMaxScale(3);
}
setMinScale(newMinScale);
}

View File

@@ -2,7 +2,6 @@ import { FederatedMouseEvent, Graphics } from "pixi.js";
import { useCallback, useState, useEffect, useRef, FC, useMemo } from "react";
import { observer } from "mobx-react-lite";
// --- Заглушки для зависимостей (замените на ваши реальные импорты) ---
import {
BACKGROUND_COLOR,
PATH_COLOR,
@@ -15,22 +14,16 @@ import { StationData } from "./types";
import { useMapData } from "./MapDataContext";
import { coordinatesToLocal } from "./utils";
import { languageStore } from "@shared";
// --- Конец заглушек ---
// --- Декларации для react-pixi ---
// (В реальном проекте типы должны быть предоставлены библиотекой @pixi/react-pixi)
declare const pixiContainer: any;
declare const pixiGraphics: any;
declare const pixiText: any;
// --- Типы ---
type HorizontalAlign = "left" | "center" | "right";
type VerticalAlign = "top" | "center" | "bottom";
type TextAlign = HorizontalAlign | `${HorizontalAlign} ${VerticalAlign}`;
type LabelAlign = "left" | "center" | "right";
// --- Утилиты ---
/**
* Преобразует текстовое позиционирование в anchor координаты.
*/
@@ -39,8 +32,6 @@ type LabelAlign = "left" | "center" | "right";
* Получает координату anchor.x из типа выравнивания.
*/
// --- Интерфейсы пропсов ---
interface StationProps {
station: StationData;
ruLabel: string | null;
@@ -83,10 +74,6 @@ const getAnchorFromOffset = (
return { x: (1 - nx) / 2, y: (1 - ny) / 2 };
};
// =========================================================================
// Компонент: Панель управления выравниванием в стиле УрФУ
// =========================================================================
const LabelAlignmentControl: FC<LabelAlignmentControlProps> = ({
scale,
currentAlign,
@@ -107,7 +94,6 @@ const LabelAlignmentControl: FC<LabelAlignmentControlProps> = ({
(g: Graphics) => {
g.clear();
// Основной фон с градиентом
g.roundRect(
-controlWidth / 2,
0,
@@ -115,9 +101,8 @@ const LabelAlignmentControl: FC<LabelAlignmentControlProps> = ({
controlHeight,
borderRadius
);
g.fill({ color: "#1a1a1a" }); // Темный фон как у УрФУ
g.fill({ color: "#1a1a1a" });
// Тонкая рамка
g.roundRect(
-controlWidth / 2,
0,
@@ -127,7 +112,6 @@ const LabelAlignmentControl: FC<LabelAlignmentControlProps> = ({
);
g.stroke({ color: "#333333", width: strokeWidth });
// Разделители между кнопками
for (let i = 1; i < 3; i++) {
const x = -controlWidth / 2 + buttonWidth * i;
g.moveTo(x, strokeWidth);
@@ -151,7 +135,7 @@ const LabelAlignmentControl: FC<LabelAlignmentControlProps> = ({
controlHeight - strokeWidth * 2,
borderRadius / 2
);
g.fill({ color: "#0066cc", alpha: 0.8 }); // Синий акцент УрФУ
g.fill({ color: "#0066cc", alpha: 0.8 });
}
},
[controlWidth, controlHeight, buttonWidth, strokeWidth, borderRadius]
@@ -230,10 +214,6 @@ const LabelAlignmentControl: FC<LabelAlignmentControlProps> = ({
);
};
// =========================================================================
// Компонент: Метка Станции (с логикой)
// =========================================================================
const StationLabel = observer(
({
station,
@@ -274,48 +254,45 @@ const StationLabel = observer(
hideTimer.current = null;
}
setIsHovered(true);
onTextHover?.(true); // Call the callback to indicate text is hovered
onTextHover?.(true);
};
const handleControlPointerEnter = () => {
// Дополнительная обработка для панели управления
if (hideTimer.current) {
clearTimeout(hideTimer.current);
hideTimer.current = null;
}
setIsControlHovered(true);
setIsHovered(true);
onTextHover?.(true); // Call the callback to indicate text/control is hovered
onTextHover?.(true);
};
const handleControlPointerLeave = () => {
setIsControlHovered(false);
// Если курсор не над основным контейнером, скрываем панель через некоторое время
if (!isHovered) {
hideTimer.current = setTimeout(() => {
setIsHovered(false);
onTextHover?.(false); // Call the callback to indicate neither text nor control is hovered
onTextHover?.(false);
}, 0);
}
};
const handlePointerLeave = () => {
// Увеличиваем время до скрытия панели и добавляем проверку
hideTimer.current = setTimeout(() => {
setIsHovered(false);
// Если курсор не над панелью управления, скрываем и её
if (!isControlHovered) {
setIsControlHovered(false);
}
onTextHover?.(false); // Call the callback to indicate text is no longer hovered
}, 100); // Увеличиваем время до скрытия панели
onTextHover?.(false);
}, 100);
};
useEffect(() => {
setPosition({ x: station.offset_x ?? 0, y: station.offset_y ?? 0 });
}, [station.offset_x, station.offset_y, station.id]);
// Функция для конвертации числового align в строковый
const convertNumericAlign = (align: number): LabelAlign => {
switch (align) {
case 0:
@@ -329,7 +306,6 @@ const StationLabel = observer(
}
};
// Функция для конвертации строкового align в числовой
const convertStringAlign = (align: LabelAlign): number => {
switch (align) {
case "left":
@@ -353,7 +329,6 @@ const StationLabel = observer(
const compensatedRuFontSize = (26 * 0.75) / scale;
const compensatedNameFontSize = (16 * 0.75) / scale;
// Измеряем ширину верхнего лейбла
useEffect(() => {
if (ruLabelRef.current && ruLabel) {
setRuLabelWidth(ruLabelRef.current.width);
@@ -386,7 +361,6 @@ const StationLabel = observer(
y: dragStartPos.current.y + dy_screen,
};
// Проверяем, изменилась ли позиция
if (
Math.abs(newPosition.x - position.x) > 0.01 ||
Math.abs(newPosition.y - position.y) > 0.01
@@ -406,7 +380,7 @@ const StationLabel = observer(
const handleAlignChange = async (align: LabelAlign) => {
setCurrentLabelAlign(align);
onLabelAlignChange?.(align);
// Сохраняем в стор
const numericAlign = convertStringAlign(align);
setStationAlign(station.id, numericAlign);
};
@@ -416,34 +390,29 @@ const StationLabel = observer(
[position.x, position.y]
);
// Функция для расчета позиции нижнего лейбла относительно ширины верхнего
const getSecondLabelPosition = (): number => {
if (!ruLabelWidth) return 0;
switch (currentLabelAlign) {
case "left":
// Позиционируем относительно левого края верхнего текста
return -ruLabelWidth / 2;
case "center":
// Центрируем относительно центра верхнего текста
return 0;
case "right":
// Позиционируем относительно правого края верхнего текста
return ruLabelWidth / 2;
default:
return 0;
}
};
// Функция для расчета anchor нижнего лейбла
const getSecondLabelAnchor = (): number => {
switch (currentLabelAlign) {
case "left":
return 0; // anchor.x = 0 (левый край)
return 0;
case "center":
return 0.5; // anchor.x = 0.5 (центр)
return 0.5;
case "right":
return 1; // anchor.x = 1 (правый край)
return 1;
default:
return 0.5;
}
@@ -522,10 +491,6 @@ const StationLabel = observer(
}
);
// =========================================================================
// Главный экспортируемый компонент: Станция
// =========================================================================
export const Station = ({
station,
ruLabel,
@@ -548,10 +513,9 @@ export const Station = ({
g.circle(coordinates.x * UP_SCALE, coordinates.y * UP_SCALE, radius);
// 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
g.fill({ color: 0x00aaff });
g.stroke({ color: 0xffffff, width: strokeWidth + 1 });
} else {
g.fill({ color: PATH_COLOR });
g.stroke({ color: BACKGROUND_COLOR, width: strokeWidth });

View File

@@ -50,7 +50,6 @@ const TransformContext = createContext<{
setScaleAtCenter: () => {},
});
// Provider component
export const TransformProvider = ({ children }: { children: ReactNode }) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [scale, setScale] = useState(1);
@@ -59,12 +58,10 @@ export const TransformProvider = ({ children }: { children: ReactNode }) => {
const screenToLocal = useCallback(
(screenX: number, screenY: number) => {
// Translate point relative to current pan position
const translatedX = (screenX - position.x) / scale;
const translatedY = (screenY - position.y) / scale;
// Rotate point around center
const cosRotation = Math.cos(-rotation); // Negative rotation to reverse transform
const cosRotation = Math.cos(-rotation);
const sinRotation = Math.sin(-rotation);
const rotatedX = translatedX * cosRotation - translatedY * sinRotation;
const rotatedY = translatedX * sinRotation + translatedY * cosRotation;
@@ -77,7 +74,6 @@ export const TransformProvider = ({ children }: { children: ReactNode }) => {
[position.x, position.y, scale, rotation]
);
// Inverse of screenToLocal
const localToScreen = useCallback(
(localX: number, localY: number) => {
const upscaledX = localX * UP_SCALE;
@@ -120,7 +116,6 @@ export const TransformProvider = ({ children }: { children: ReactNode }) => {
(currentFromPosition.x - center.x) * sinDelta,
};
// Update both rotation and position in a single batch to avoid stale closure
setRotation(to);
setPosition(newPosition);
},
@@ -150,13 +145,11 @@ export const TransformProvider = ({ children }: { children: ReactNode }) => {
const cosRot = Math.cos(selectedRotation);
const sinRot = Math.sin(selectedRotation);
// Translate point relative to center, rotate, then translate back
const dx = newPosition.x;
const dy = newPosition.y;
newPosition.x = dx * cosRot - dy * sinRot + center.x;
newPosition.y = dx * sinRot + dy * cosRot + center.y;
// Batch state updates to avoid intermediate renders
setPosition(newPosition);
setRotation(selectedRotation);
setScale(selectedScale);
@@ -184,7 +177,6 @@ export const TransformProvider = ({ children }: { children: ReactNode }) => {
);
const setScaleOnly = useCallback((newScale: number) => {
// Изменяем только масштаб, не трогая позицию и поворот
setScale(newScale);
}, []);
@@ -237,7 +229,6 @@ export const TransformProvider = ({ children }: { children: ReactNode }) => {
);
};
// Custom hook for easy access to transform values
export const useTransform = () => {
const context = useContext(TransformContext);
if (!context) {

View File

@@ -53,13 +53,11 @@ export const WebGLMap = observer(() => {
const cameraAnimationStore = useCameraAnimationStore();
// Ref для хранения ограничений масштаба
const scaleLimitsRef = useRef({
min: null as number | null,
max: null as number | null,
});
// Обновляем ограничения масштаба при изменении routeData
useEffect(() => {
if (
routeData?.scale_min !== undefined &&
@@ -72,7 +70,6 @@ export const WebGLMap = observer(() => {
}
}, [routeData?.scale_min, routeData?.scale_max]);
// Функция для ограничения масштаба значениями с бекенда
const clampScale = useCallback((value: number) => {
const { min, max } = scaleLimitsRef.current;
@@ -90,7 +87,6 @@ export const WebGLMap = observer(() => {
const setPositionRef = useRef(setPosition);
const setScaleRef = useRef(setScale);
// Обновляем refs при изменении функций
useEffect(() => {
setPositionRef.current = setPosition;
}, [setPosition]);
@@ -99,7 +95,6 @@ export const WebGLMap = observer(() => {
setScaleRef.current = setScale;
}, [setScale]);
// Логирование данных маршрута для отладки
useEffect(() => {
if (routeData) {
}
@@ -124,7 +119,6 @@ export const WebGLMap = observer(() => {
setPositionImmediate: setYellowDotPositionImmediate,
} = useAnimatedPolarPosition(0, 0, 800);
// Build transformed route path (map coords)
const routePath = useMemo(() => {
if (!routeData?.path || routeData?.path.length === 0)
return new Float32Array();
@@ -180,7 +174,6 @@ export const WebGLMap = observer(() => {
rotationAngle,
]);
// Настройка CameraAnimationStore callback - только один раз при монтировании
useEffect(() => {
const callback = (newPos: { x: number; y: number }, newZoom: number) => {
setPosition(newPos);
@@ -189,15 +182,13 @@ export const WebGLMap = observer(() => {
cameraAnimationStore.setUpdateCallback(callback);
// Синхронизируем начальное состояние только один раз
cameraAnimationStore.syncState(position, scale);
return () => {
cameraAnimationStore.setUpdateCallback(null);
};
}, []); // Пустой массив - выполняется только при монтировании
}, []);
// Установка границ зума
useEffect(() => {
if (
routeData?.scale_min !== undefined &&
@@ -208,28 +199,23 @@ export const WebGLMap = observer(() => {
}
}, [routeData?.scale_min, routeData?.scale_max, cameraAnimationStore]);
// Автоматический режим - таймер для включения через 5 секунд бездействия
useEffect(() => {
const interval = setInterval(() => {
const timeSinceActivity = Date.now() - userActivityTimestamp;
if (timeSinceActivity >= 5000 && !isAutoMode) {
// 5 секунд бездействия - включаем авто режим
setIsAutoMode(true);
}
}, 1000); // Проверяем каждую секунду
}, 1000);
return () => clearInterval(interval);
}, [userActivityTimestamp, isAutoMode, setIsAutoMode]);
// Следование за желтой точкой с зумом при включенном авто режиме
useEffect(() => {
// Пропускаем обновление если анимация уже идет
if (cameraAnimationStore.isActivelyAnimating) {
return;
}
if (isAutoMode && transformedTramCoords && screenCenter) {
// Преобразуем станции в формат для CameraAnimationStore
const transformedStations = stationData
? stationData
.map((station: any) => {
@@ -270,10 +256,8 @@ export const WebGLMap = observer(() => {
cameraAnimationStore.setMaxZoom(scaleLimitsRef.current!.max);
cameraAnimationStore.setMinZoom(scaleLimitsRef.current!.min);
// Синхронизируем текущее состояние камеры перед запуском анимации
cameraAnimationStore.syncState(positionRef.current, scaleRef.current);
// Запускаем анимацию к желтой точке
cameraAnimationStore.followTram(
transformedTramCoords,
screenCenter,
@@ -293,7 +277,6 @@ export const WebGLMap = observer(() => {
rotationAngle,
]);
// Station label overlay positions (DOM overlay)
const stationLabels = useMemo(() => {
if (!stationData || !routeData)
return [] as Array<{ x: number; y: number; name: string; sub?: string }>;
@@ -356,7 +339,6 @@ export const WebGLMap = observer(() => {
selectedLanguage as any,
]);
// Build transformed stations (map coords)
const stationPoints = useMemo(() => {
if (!stationData || !routeData) return new Float32Array();
const centerLat = routeData.center_latitude;
@@ -386,7 +368,6 @@ export const WebGLMap = observer(() => {
rotationAngle,
]);
// Build transformed sights (map coords)
const sightPoints = useMemo(() => {
if (!sightData || !routeData) return new Float32Array();
const centerLat = routeData.center_latitude;
@@ -530,8 +511,6 @@ export const WebGLMap = observer(() => {
const handleResize = () => {
const changed = resizeCanvasToDisplaySize(canvas);
if (!gl) return;
// Update screen center when canvas size changes
// Use physical pixels (canvas.width) instead of CSS pixels
setScreenCenter({
x: canvas.width / 2,
y: canvas.height / 2,
@@ -567,7 +546,6 @@ export const WebGLMap = observer(() => {
const rx = x * cos - y * sin;
const ry = x * sin + y * cos;
// В авторежиме используем анимацию, иначе мгновенное обновление
if (isAutoMode) {
animateYellowDotTo(rx, ry);
} else {
@@ -666,21 +644,18 @@ export const WebGLMap = observer(() => {
const vertexCount = routePath.length / 2;
if (vertexCount > 1) {
// Generate thick line geometry using triangles with proper joins
const generateThickLine = (points: Float32Array, width: number) => {
const vertices: number[] = [];
const halfWidth = width / 2;
if (points.length < 4) return new Float32Array();
// Process each segment
for (let i = 0; i < points.length - 2; i += 2) {
const x1 = points[i];
const y1 = points[i + 1];
const x2 = points[i + 2];
const y2 = points[i + 3];
// Calculate perpendicular vector
const dx = x2 - x1;
const dy = y2 - y1;
const length = Math.sqrt(dx * dx + dy * dy);
@@ -689,18 +664,14 @@ export const WebGLMap = observer(() => {
const perpX = (-dy / length) * halfWidth;
const perpY = (dx / length) * halfWidth;
// Create quad (two triangles) for this line segment
// Triangle 1
vertices.push(x1 + perpX, y1 + perpY);
vertices.push(x1 - perpX, y1 - perpY);
vertices.push(x2 + perpX, y2 + perpY);
// Triangle 2
vertices.push(x1 - perpX, y1 - perpY);
vertices.push(x2 - perpX, y2 - perpY);
vertices.push(x2 + perpX, y2 + perpY);
// Add simple join triangles to fill gaps
if (i < points.length - 4) {
const x3 = points[i + 4];
const y3 = points[i + 5];
@@ -712,7 +683,6 @@ export const WebGLMap = observer(() => {
const perpX2 = (-dy2 / length2) * halfWidth;
const perpY2 = (dx2 / length2) * halfWidth;
// Simple join - just connect the endpoints
vertices.push(x2 + perpX, y2 + perpY);
vertices.push(x2 - perpX, y2 - perpY);
vertices.push(x2 + perpX2, y2 + perpY2);
@@ -734,22 +704,18 @@ export const WebGLMap = observer(() => {
gl.uniform4f(uniforms.u_color, r1, g1, b1, 1);
if (tramSegIndex >= 0) {
// Используем точную позицию желтой точки для определения конца красной линии
const animatedPos = animatedYellowDotPosition;
if (
animatedPos &&
animatedPos.x !== undefined &&
animatedPos.y !== undefined
) {
// Создаем массив точек от начала маршрута до позиции желтой точки
const passedPoints: number[] = [];
// Добавляем все точки до текущего сегмента
for (let i = 0; i <= tramSegIndex; i++) {
passedPoints.push(routePath[i * 2], routePath[i * 2 + 1]);
}
// Добавляем точную позицию желтой точки как конечную точку
passedPoints.push(animatedPos.x, animatedPos.y);
if (passedPoints.length >= 4) {
@@ -768,7 +734,6 @@ export const WebGLMap = observer(() => {
const b2 = (UNPASSED_STATION_COLOR & 0xff) / 255;
gl.uniform4f(uniforms.u_color, r2, g2, b2, 1);
// Серая линия начинается точно от позиции желтой точки
const animatedPos = animatedYellowDotPosition;
if (
animatedPos &&
@@ -777,10 +742,8 @@ export const WebGLMap = observer(() => {
) {
const unpassedPoints: number[] = [];
// Добавляем позицию желтой точки как начальную точку серой линии
unpassedPoints.push(animatedPos.x, animatedPos.y);
// Добавляем все точки после текущего сегмента
for (let i = tramSegIndex + 1; i < vertexCount; i++) {
unpassedPoints.push(routePath[i * 2], routePath[i * 2 + 1]);
}
@@ -796,7 +759,6 @@ export const WebGLMap = observer(() => {
}
}
// Draw stations
if (stationPoints.length > 0) {
gl.useProgram(pprog);
const a_pos_pts = gl.getAttribLocation(pprog, "a_pos");
@@ -814,7 +776,6 @@ export const WebGLMap = observer(() => {
gl.enableVertexAttribArray(a_pos_pts);
gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
// Draw station outlines (black background)
gl.uniform1f(u_pointSize, 10 * scale * 1.5);
const r_outline = ((BACKGROUND_COLOR >> 16) & 0xff) / 255;
const g_outline = ((BACKGROUND_COLOR >> 8) & 0xff) / 255;
@@ -822,15 +783,12 @@ export const WebGLMap = observer(() => {
gl.uniform4f(u_color_pts, r_outline, g_outline, b_outline, 1);
gl.drawArrays(gl.POINTS, 0, stationPoints.length / 2);
// Draw station cores (colored based on passed/unpassed)
gl.uniform1f(u_pointSize, 8.0 * scale * 1.5);
// Draw passed stations (red)
if (tramSegIndex >= 0) {
const passedStations = [];
for (let i = 0; i < stationData.length; i++) {
if (i <= tramSegIndex) {
// @ts-ignore
passedStations.push(stationPoints[i * 2], stationPoints[i * 2 + 1]);
}
}
@@ -848,13 +806,11 @@ export const WebGLMap = observer(() => {
}
}
// Draw unpassed stations (gray)
if (tramSegIndex >= 0) {
const unpassedStations = [];
for (let i = 0; i < stationData.length; i++) {
if (i > tramSegIndex) {
unpassedStations.push(
// @ts-ignore
stationPoints[i * 2],
stationPoints[i * 2 + 1]
);
@@ -873,7 +829,6 @@ export const WebGLMap = observer(() => {
gl.drawArrays(gl.POINTS, 0, unpassedStations.length / 2);
}
} else {
// If no tram position, draw all stations as unpassed
const r_unpassed = ((UNPASSED_STATION_COLOR >> 16) & 0xff) / 255;
const g_unpassed = ((UNPASSED_STATION_COLOR >> 8) & 0xff) / 255;
const b_unpassed = (UNPASSED_STATION_COLOR & 0xff) / 255;
@@ -1015,7 +970,6 @@ export const WebGLMap = observer(() => {
if (passedStations.length)
gl.drawArrays(gl.POINTS, 0, passedStations.length / 2);
// Draw black dots with white outline for terminal stations (startStopId and endStopId) - 5x larger
if (
stationData &&
stationData.length > 0 &&
@@ -1028,7 +982,6 @@ export const WebGLMap = observer(() => {
const cos = Math.cos(rotationAngle);
const sin = Math.sin(rotationAngle);
// Find terminal stations using startStopId and endStopId from context
const startStationData = stationData.find(
(station) => station.id.toString() === apiStore.context?.startStopId
);
@@ -1038,7 +991,6 @@ export const WebGLMap = observer(() => {
const terminalStations: number[] = [];
// Transform start station coordinates if found
if (startStationData) {
const startLocal = coordinatesToLocal(
startStationData.latitude - centerLat,
@@ -1051,7 +1003,6 @@ export const WebGLMap = observer(() => {
terminalStations.push(startRx, startRy);
}
// Transform end station coordinates if found
if (endStationData) {
const endLocal = coordinatesToLocal(
endStationData.latitude - centerLat,
@@ -1065,12 +1016,10 @@ export const WebGLMap = observer(() => {
}
if (terminalStations.length > 0) {
// Determine if each terminal station is passed
const terminalStationData: any[] = [];
if (startStationData) terminalStationData.push(startStationData);
if (endStationData) terminalStationData.push(endStationData);
// Get tram segment index for comparison
let tramSegIndex = -1;
const coords: any = apiStore?.context?.currentCoordinates;
if (coords && centerLat !== undefined && centerLon !== undefined) {
@@ -1085,7 +1034,6 @@ export const WebGLMap = observer(() => {
const tx = wx * cosR - wy * sinR;
const ty = wx * sinR + wy * cosR;
// Find closest segment to tram position
let best = -1;
let bestD = Infinity;
for (let i = 0; i < routePath.length - 2; i += 2) {
@@ -1110,7 +1058,6 @@ export const WebGLMap = observer(() => {
tramSegIndex = best;
}
// Check if each terminal station is passed
const isStartPassed = startStationData
? (() => {
const sx = terminalStations[0];
@@ -1186,46 +1133,41 @@ export const WebGLMap = observer(() => {
gl.enableVertexAttribArray(a_pos_pts);
gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
// Draw colored outline based on passed status - 24 pixels (x2)
gl.uniform1f(u_pointSize, 18.0 * scale);
if (startStationData && endStationData) {
// Both stations - draw each with its own color
if (isStartPassed) {
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0); // Ярко-красный для пройденных
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
} else {
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0); // Светло-серый для непройденных
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
}
gl.drawArrays(gl.POINTS, 0, 1); // Draw start station
gl.drawArrays(gl.POINTS, 0, 1);
if (isEndPassed) {
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0); // Ярко-красный для пройденных
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
} else {
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0); // Светло-серый для непройденных
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
}
gl.drawArrays(gl.POINTS, 1, 1); // Draw end station
gl.drawArrays(gl.POINTS, 1, 1);
} else {
// Single station - use appropriate color
const isPassed = startStationData ? isStartPassed : isEndPassed;
if (isPassed) {
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0); // Ярко-красный для пройденных
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
} else {
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0); // Светло-серый для непройденных
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
}
gl.drawArrays(gl.POINTS, 0, terminalStations.length / 2);
}
// Draw dark center - 12 pixels (x2)
gl.uniform1f(u_pointSize, 11.0 * scale);
const r_center = ((BACKGROUND_COLOR >> 16) & 0xff) / 255;
const g_center = ((BACKGROUND_COLOR >> 8) & 0xff) / 255;
const b_center = (BACKGROUND_COLOR & 0xff) / 255;
gl.uniform4f(u_color_pts, r_center, g_center, b_center, 1.0); // Dark color
gl.uniform4f(u_color_pts, r_center, g_center, b_center, 1.0);
gl.drawArrays(gl.POINTS, 0, terminalStations.length / 2);
}
}
}
// Draw yellow dot for tram position
if (animatedYellowDotPosition) {
const rx = animatedYellowDotPosition.x;
const ry = animatedYellowDotPosition.y;
@@ -1327,7 +1269,6 @@ export const WebGLMap = observer(() => {
});
const onPointerDown = (e: PointerEvent) => {
// Отслеживаем активность пользователя
updateUserActivity();
if (isAutoMode) {
setIsAutoMode(false);
@@ -1360,7 +1301,6 @@ export const WebGLMap = observer(() => {
const onPointerMove = (e: PointerEvent) => {
if (!activePointers.has(e.pointerId)) return;
// Отслеживаем активность пользователя
updateUserActivity();
const rect = canvas.getBoundingClientRect();
@@ -1386,7 +1326,6 @@ export const WebGLMap = observer(() => {
};
}
// Process the pinch gesture
if (pinchStart) {
const currentDistance = getDistance(p1, p2);
const zoomFactor = currentDistance / pinchStart.distance;
@@ -1405,7 +1344,6 @@ export const WebGLMap = observer(() => {
} else if (isDragging && activePointers.size === 1) {
const p = Array.from(activePointers.values())[0];
// Проверяем валидность значений
if (
!startMouse ||
!startPos ||
@@ -1433,7 +1371,6 @@ export const WebGLMap = observer(() => {
};
const onPointerUp = (e: PointerEvent) => {
// Отслеживаем активность пользователя
updateUserActivity();
canvas.releasePointerCapture(e.pointerId);
@@ -1453,7 +1390,6 @@ export const WebGLMap = observer(() => {
};
const onPointerCancel = (e: PointerEvent) => {
// Handle pointer cancellation (e.g., when touch is interrupted)
updateUserActivity();
canvas.releasePointerCapture(e.pointerId);
activePointers.delete(e.pointerId);
@@ -1467,7 +1403,6 @@ export const WebGLMap = observer(() => {
const onWheel = (e: WheelEvent) => {
e.preventDefault();
// Отслеживаем активность пользователя
updateUserActivity();
if (isAutoMode) {
setIsAutoMode(false);
@@ -1475,7 +1410,6 @@ export const WebGLMap = observer(() => {
cameraAnimationStore.stopAnimation();
const rect = canvas.getBoundingClientRect();
// Convert mouse coordinates from CSS pixels to physical canvas pixels
const mouseX =
(e.clientX - rect.left) * (canvas.width / canvas.clientWidth);
const mouseY =
@@ -1582,7 +1516,6 @@ export const WebGLMap = observer(() => {
const sy = (ry * scale + position.y) / dpr;
const size = 30;
// Обработчик клика для выбора достопримечательности
const handleSightClick = () => {
const {
setSelectedSightId,