From b837aae71124f11ed0bda013b65517e3a58e8568 Mon Sep 17 00:00:00 2001 From: itoshi Date: Tue, 27 May 2025 20:57:14 +0300 Subject: [PATCH] feat: Add `language switcher` for preview route --- .../modals/StationEditModal/index.tsx | 2 +- src/locales/ru/translation.json | 6 +- src/pages/route-preview/InfiniteCanvas.tsx | 356 +++++++++------- src/pages/route-preview/LeftSidebar.tsx | 110 +++-- src/pages/route-preview/MapDataContext.tsx | 403 +++++++++--------- src/pages/route-preview/RightSidebar.tsx | 383 +++++++++-------- src/pages/route-preview/TransformContext.tsx | 294 +++++++------ src/pages/route-preview/TravelPath.tsx | 53 ++- src/pages/route-preview/Widgets.tsx | 66 +-- src/pages/route-preview/index.tsx | 259 +++++------ src/pages/route/edit.tsx | 6 +- src/pages/route/show.tsx | 13 +- src/providers/data.ts | 14 + 13 files changed, 1113 insertions(+), 852 deletions(-) diff --git a/src/components/modals/StationEditModal/index.tsx b/src/components/modals/StationEditModal/index.tsx index aa295ff..a2715a0 100644 --- a/src/components/modals/StationEditModal/index.tsx +++ b/src/components/modals/StationEditModal/index.tsx @@ -121,7 +121,7 @@ export const StationEditModal = observer(() => { > Редактирование станции} + title={Редактирование остановки} saveButtonProps={saveButtonProps} > { - state = { hasError: false }; - - static getDerivedStateFromError() { - return { hasError: true }; - } - - componentDidCatch(error: Error, info: React.ErrorInfo) { - console.error("Error caught:", error, info); - } - - render() { - return this.state.hasError ?

Whoopsie Daisy!

: this.props.children; - } +class ErrorBoundary extends Component< + { children: ReactNode }, + { hasError: boolean } +> { + state = { hasError: false }; + + static getDerivedStateFromError() { + return { hasError: true }; } + componentDidCatch(error: Error, info: React.ErrorInfo) { + console.error("Error caught:", error, info); + } -export function InfiniteCanvas({children} : Readonly<{children?: ReactNode}>) { - const { position, setPosition, scale, setScale, rotation, setRotation, setScreenCenter, screenCenter } = useTransform(); - const { routeData, originalRouteData } = useMapData(); + render() { + return this.state.hasError ?

Whoopsie Daisy!

: this.props.children; + } +} - const applicationRef = useApplication(); - - const [isDragging, setIsDragging] = useState(false); - const [startMousePosition, setStartMousePosition] = useState({ x: 0, y: 0 }); - const [startRotation, setStartRotation] = useState(0); - const [startPosition, setStartPosition] = useState({ x: 0, y: 0 }); +export function InfiniteCanvas({ + children, +}: Readonly<{ children?: ReactNode }>) { + const { + position, + setPosition, + scale, + setScale, + rotation, + setRotation, + setScreenCenter, + screenCenter, + } = useTransform(); + const { routeData, originalRouteData } = useMapData(); - useEffect(() => { - const canvas = applicationRef?.app.canvas; - if (!canvas) return; - const canvasRect = canvas.getBoundingClientRect(); - const canvasLeft = canvasRect?.left ?? 0; - const canvasTop = canvasRect?.top ?? 0; - const centerX = window.innerWidth / 2 - canvasLeft; - const centerY = window.innerHeight / 2 - canvasTop; - setScreenCenter({x: centerX, y: centerY}); - }, [applicationRef?.app.canvas, window.innerWidth, window.innerHeight]); + const applicationRef = useApplication(); - const handlePointerDown = (e: FederatedMouseEvent) => { - setIsDragging(true); - setStartPosition({ - x: position.x, - y: position.y - }); - setStartMousePosition({ - x: e.globalX, - y: e.globalY - }); - setStartRotation(rotation); - e.stopPropagation(); - }; + const [isDragging, setIsDragging] = useState(false); + const [startMousePosition, setStartMousePosition] = useState({ x: 0, y: 0 }); + const [startRotation, setStartRotation] = useState(0); + const [startPosition, setStartPosition] = useState({ x: 0, y: 0 }); + // Флаг для предотвращения конфликта между пользовательским вводом и данными маршрута + const [isUserInteracting, setIsUserInteracting] = useState(false); - useEffect(() => { - setRotation((originalRouteData?.rotate ?? 0) * Math.PI / 180); - }, [originalRouteData?.rotate]); - // Get canvas element and its dimensions/position - const handlePointerMove = (e: FederatedMouseEvent) => { - if (!isDragging) return; + // Реф для отслеживания последнего значения originalRouteData?.rotate + const lastOriginalRotation = useRef(undefined); - if (e.shiftKey) { - const center = screenCenter ?? {x: 0, y: 0}; - const startAngle = Math.atan2(startMousePosition.y - center.y, startMousePosition.x - center.x); - const currentAngle = Math.atan2(e.globalY - center.y, e.globalX - center.x); + useEffect(() => { + const canvas = applicationRef?.app.canvas; + if (!canvas) return; - // Calculate rotation difference in radians - const rotationDiff = currentAngle - startAngle; + const canvasRect = canvas.getBoundingClientRect(); + const canvasLeft = canvasRect.left; + const canvasTop = canvasRect.top; + const centerX = window.innerWidth / 2 - canvasLeft; + const centerY = window.innerHeight / 2 - canvasTop; + setScreenCenter({ x: centerX, y: centerY }); + }, [applicationRef?.app.canvas, setScreenCenter]); - // Update rotation - setRotation(startRotation + rotationDiff); + const handlePointerDown = (e: FederatedMouseEvent) => { + setIsDragging(true); + setIsUserInteracting(true); // Устанавливаем флаг взаимодействия пользователя + setStartPosition({ + x: position.x, + y: position.y, + }); + setStartMousePosition({ + x: e.globalX, + y: e.globalY, + }); + setStartRotation(rotation); + e.stopPropagation(); + }; - const cosDelta = Math.cos(rotationDiff); - const sinDelta = Math.sin(rotationDiff); - - setPosition({ - x: center.x * (1 - cosDelta) + startPosition.x * cosDelta + (center.y - startPosition.y) * sinDelta, - y: center.y * (1 - cosDelta) + startPosition.y * cosDelta + (startPosition.x - center.x) * sinDelta - }); - - } else { - setRotation(startRotation); - setPosition({ - x: startPosition.x - startMousePosition.x + e.globalX, - y: startPosition.y - startMousePosition.y + e.globalY - }); - } - e.stopPropagation(); - }; + // Устанавливаем rotation только при изменении originalRouteData и отсутствии взаимодействия пользователя + useEffect(() => { + const newRotation = originalRouteData?.rotate ?? 0; - // Handle mouse up - const handlePointerUp = (e: FederatedMouseEvent) => { - setIsDragging(false); - e.stopPropagation(); - }; - // Handle mouse wheel for zooming - const handleWheel = (e: FederatedWheelEvent) => { - e.stopPropagation(); - - // Get mouse position relative to canvas - const mouseX = e.globalX - position.x; - const mouseY = e.globalY - position.y; + // Обновляем rotation только если: + // 1. Пользователь не взаимодействует с канвасом + // 2. Значение действительно изменилось + if (!isUserInteracting && lastOriginalRotation.current !== newRotation) { + setRotation((newRotation * Math.PI) / 180); + lastOriginalRotation.current = newRotation; + } + }, [originalRouteData?.rotate, isUserInteracting, setRotation]); - // Calculate new scale - const scaleMin = (routeData?.scale_min ?? 10)/SCALE_FACTOR; - const scaleMax = (routeData?.scale_max ?? 20)/SCALE_FACTOR; + const handlePointerMove = (e: FederatedMouseEvent) => { + if (!isDragging) return; - let zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; // Zoom out/in - //const newScale = scale * zoomFactor; - const newScale = Math.max(scaleMin, Math.min(scaleMax, scale * zoomFactor)); - zoomFactor = newScale / scale; + if (e.shiftKey) { + const center = screenCenter ?? { x: 0, y: 0 }; + const startAngle = Math.atan2( + startMousePosition.y - center.y, + startMousePosition.x - center.x + ); + const currentAngle = Math.atan2( + e.globalY - center.y, + e.globalX - center.x + ); - if (scale === newScale) { - return; - } + // Calculate rotation difference in radians + const rotationDiff = currentAngle - startAngle; - // Update position to zoom towards mouse cursor - setPosition({ - x: position.x + mouseX * (1 - zoomFactor), - y: position.y + mouseY * (1 - zoomFactor) - }); + // Update rotation + setRotation(startRotation + rotationDiff); - setScale(newScale); - }; + const cosDelta = Math.cos(rotationDiff); + const sinDelta = Math.sin(rotationDiff); - useEffect(() => { - applicationRef?.app.render(); - console.log(position, scale, rotation); - }, [position, scale, rotation]); + setPosition({ + x: + center.x * (1 - cosDelta) + + startPosition.x * cosDelta + + (center.y - startPosition.y) * sinDelta, + y: + center.y * (1 - cosDelta) + + startPosition.y * cosDelta + + (startPosition.x - center.x) * sinDelta, + }); + } else { + setRotation(startRotation); + setPosition({ + x: startPosition.x - startMousePosition.x + e.globalX, + y: startPosition.y - startMousePosition.y + e.globalY, + }); + } + e.stopPropagation(); + }; - - return ( - - {applicationRef?.app && ( - { - const canvas = applicationRef.app.canvas; - g.clear(); - g.rect(0, 0, canvas?.width ?? 0, canvas?.height ?? 0); - g.fill("#111"); - }} - eventMode={'static'} - interactive - onPointerDown={handlePointerDown} - onGlobalPointerMove={handlePointerMove} - onPointerUp={handlePointerUp} - onPointerUpOutside={handlePointerUp} - onWheel={handleWheel} - /> - )} - - {children} - - {/* Show center of the screen. - { + setIsDragging(false); + // Сбрасываем флаг взаимодействия через небольшую задержку + // чтобы избежать немедленного срабатывания useEffect + setTimeout(() => { + setIsUserInteracting(false); + }, 100); + e.stopPropagation(); + }; - draw={(g) => { - g.clear(); - const center = screenCenter ?? {x: 0, y: 0}; - g.circle(center.x, center.y, 1); - g.fill("#fff"); - }} - /> */} - - ); -} \ No newline at end of file + const handleWheel = (e: FederatedWheelEvent) => { + e.stopPropagation(); + 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 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), + }); + + setScale(newScale); + + // Сбрасываем флаг взаимодействия через задержку + setTimeout(() => { + setIsUserInteracting(false); + }, 100); + }; + + useEffect(() => { + applicationRef?.app.render(); + console.log(position, scale, rotation); + }, [position, scale, rotation]); + + return ( + + {applicationRef?.app && ( + { + const canvas = applicationRef.app.canvas; + g.clear(); + g.rect(0, 0, canvas?.width ?? 0, canvas?.height ?? 0); + g.fill("#111"); + }} + eventMode={"static"} + interactive + onPointerDown={handlePointerDown} + onGlobalPointerMove={handlePointerMove} + onPointerUp={handlePointerUp} + onPointerUpOutside={handlePointerUp} + onWheel={handleWheel} + /> + )} + + {children} + + {/* Show center of the screen. + { + g.clear(); + const center = screenCenter ?? {x: 0, y: 0}; + g.circle(center.x, center.y, 1); + g.fill("#fff"); + }} + /> */} + + ); +} diff --git a/src/pages/route-preview/LeftSidebar.tsx b/src/pages/route-preview/LeftSidebar.tsx index 49c02ff..0e87c0e 100644 --- a/src/pages/route-preview/LeftSidebar.tsx +++ b/src/pages/route-preview/LeftSidebar.tsx @@ -1,33 +1,89 @@ import { Stack, Typography, Button } from "@mui/material"; +import { useNavigate, useNavigationType } from "react-router"; + export function LeftSidebar() { - return ( - - - logo - - При поддержке Правительства - Санкт-Петербурга - - + const navigate = useNavigate(); + const navigationType = useNavigationType(); // PUSH, POP, REPLACE - - - - - + const handleBack = () => { + if (navigationType === "PUSH") { + navigate(-1); + } else { + navigate("/route"); + } + }; - - logo - - - #ВсемПоПути + return ( + + - - - ); -} \ No newline at end of file + + logo + + При поддержке Правительства Санкт-Петербурга + + + + + + + + + + logo + + + + #ВсемПоПути + + + ); +} diff --git a/src/pages/route-preview/MapDataContext.tsx b/src/pages/route-preview/MapDataContext.tsx index 23aa2c3..32375cd 100644 --- a/src/pages/route-preview/MapDataContext.tsx +++ b/src/pages/route-preview/MapDataContext.tsx @@ -1,4 +1,4 @@ -import { useCustom, useApiUrl } from "@refinedev/core"; +import { useApiUrl } from "@refinedev/core"; import { useParams } from "react-router"; import { createContext, @@ -16,6 +16,9 @@ import { StationPatchData, } from "./types"; import { axiosInstance } from "../../providers/data"; +import { languageStore, META_LANGUAGE } from "@/store/LanguageStore"; +import { observer } from "mobx-react-lite"; +import { axiosInstanceForGet } from "@/providers/data"; const MapDataContext = createContext<{ originalRouteData?: RouteData; @@ -57,210 +60,230 @@ const MapDataContext = createContext<{ saveChanges: () => {}, }); -export function MapDataProvider({ - children, -}: Readonly<{ children: ReactNode }>) { - const { id: routeId } = useParams<{ id: string }>(); - const apiUrl = useApiUrl(); +export const MapDataProvider = observer( + ({ children }: Readonly<{ children: ReactNode }>) => { + const { id: routeId } = useParams<{ id: string }>(); + const apiUrl = useApiUrl(); - const [originalRouteData, setOriginalRouteData] = useState(); - const [originalStationData, setOriginalStationData] = - useState(); - const [originalSightData, setOriginalSightData] = useState(); + const [originalRouteData, setOriginalRouteData] = useState(); + const [originalStationData, setOriginalStationData] = + useState(); + const [originalSightData, setOriginalSightData] = useState(); - const [routeData, setRouteData] = useState(); - const [stationData, setStationData] = useState(); - const [sightData, setSightData] = useState(); + const [routeData, setRouteData] = useState(); + const [stationData, setStationData] = useState(); + const [sightData, setSightData] = useState(); - const [routeChanges, setRouteChanges] = useState({} as RouteData); - const [stationChanges, setStationChanges] = useState([]); - const [sightChanges, setSightChanges] = useState([]); + const [routeChanges, setRouteChanges] = useState>({}); + const [stationChanges, setStationChanges] = useState( + [] + ); + const [sightChanges, setSightChanges] = useState([]); + const { language } = languageStore; - const { data: routeQuery, isLoading: isRouteLoading } = useCustom({ - url: `${apiUrl}/route/${routeId}`, - method: "get", - }); - const { data: stationQuery, isLoading: isStationLoading } = useCustom({ - url: `${apiUrl}/route/${routeId}/station`, - method: "get", - }); - const { data: sightQuery, isLoading: isSightLoading } = useCustom({ - url: `${apiUrl}/route/${routeId}/sight`, - method: "get", - }); - useEffect(() => { - // if not undefined, set original data - if (routeQuery?.data) setOriginalRouteData(routeQuery.data as RouteData); - if (stationQuery?.data) - setOriginalStationData(stationQuery.data as StationData[]); - if (sightQuery?.data) setOriginalSightData(sightQuery.data as SightData[]); - console.log("queries", routeQuery, stationQuery, sightQuery); - }, [routeQuery, stationQuery, sightQuery]); + const [isRouteLoading, setIsRouteLoading] = useState(true); + const [isStationLoading, setIsStationLoading] = useState(true); + const [isSightLoading, setIsSightLoading] = useState(true); - useEffect(() => { - // combine changes with original data - if (originalRouteData) - setRouteData({ ...originalRouteData, ...routeChanges }); - if (originalStationData) setStationData(originalStationData); - if (originalSightData) setSightData(originalSightData); - }, [ - originalRouteData, - originalStationData, - originalSightData, - routeChanges, - stationChanges, - sightChanges, - ]); + useEffect(() => { + const fetchData = async () => { + try { + setIsRouteLoading(true); + setIsStationLoading(true); + setIsSightLoading(true); - function setScaleRange(min: number, max: number) { - setRouteChanges((prev) => { - return { ...prev, scale_min: min, scale_max: max }; - }); - } + const [routeResponse, stationResponse, sightResponse] = + await Promise.all([ + axiosInstanceForGet.get(`/route/${routeId}`), + axiosInstanceForGet.get(`/route/${routeId}/station`), + axiosInstanceForGet.get(`/route/${routeId}/sight`), + ]); - function setMapRotation(rotation: number) { - setRouteChanges((prev) => { - return { ...prev, rotate: rotation }; - }); - } + setOriginalRouteData(routeResponse.data as RouteData); + setOriginalStationData(stationResponse.data as StationData[]); + setOriginalSightData(sightResponse.data as SightData[]); - function setMapCenter(x: number, y: number) { - setRouteChanges((prev) => { - return { ...prev, center_latitude: x, center_longitude: y }; - }); - } - - async function saveChanges() { - await axiosInstance.patch(`/route/${routeId}`, routeData); - await saveStationChanges(); - await saveSightChanges(); - } - - async function saveStationChanges() { - for (const station of stationChanges) { - const response = await axiosInstance.patch( - `/route/${routeId}/station`, - station - ); - } - } - - async function saveSightChanges() { - console.log("sightChanges", sightChanges); - for (const sight of sightChanges) { - const response = await axiosInstance.patch( - `/route/${routeId}/sight`, - sight - ); - } - } - - function setStationOffset(stationId: number, x: number, y: number) { - setStationChanges((prev) => { - let found = prev.find((station) => station.station_id === stationId); - if (found) { - found.offset_x = x; - found.offset_y = y; - - return prev.map((station) => { - if (station.station_id === stationId) { - return found; - } - return station; - }); - } else { - const foundStation = stationData?.find( - (station) => station.id === stationId - ); - if (foundStation) { - return [ - ...prev, - { - station_id: stationId, - offset_x: x, - offset_y: y, - transfers: foundStation.transfers, - }, - ]; + setIsRouteLoading(false); + setIsStationLoading(false); + setIsSightLoading(false); + } catch (error) { + console.error("Error fetching data:", error); + setIsRouteLoading(false); + setIsStationLoading(false); + setIsSightLoading(false); } - return prev; - } - }); - } + }; - function setSightCoordinates( - sightId: number, - latitude: number, - longitude: number - ) { - setSightChanges((prev) => { - let found = prev.find((sight) => sight.sight_id === sightId); - if (found) { - found.latitude = latitude; - found.longitude = longitude; + fetchData(); + }, [routeId, language]); - return prev.map((sight) => { - if (sight.sight_id === sightId) { - return found; - } - return sight; - }); - } else { - const foundSight = sightData?.find((sight) => sight.id === sightId); - if (foundSight) { - return [ - ...prev, - { - sight_id: sightId, - latitude, - longitude, - }, - ]; - } - return prev; - } - }); - } - - useEffect(() => { - console.log("sightChanges", sightChanges); - }, [sightChanges]); - - const value = useMemo( - () => ({ - originalRouteData: originalRouteData, - originalStationData: originalStationData, - originalSightData: originalSightData, - routeData: routeData, - stationData: stationData, - sightData: sightData, - isRouteLoading, - isStationLoading, - isSightLoading, - setScaleRange, - setMapRotation, - setMapCenter, - saveChanges, - setStationOffset, - setSightCoordinates, - }), - [ + useEffect(() => { + // combine changes with original data + if (originalRouteData) + setRouteData({ ...originalRouteData, ...routeChanges }); + if (originalStationData) setStationData(originalStationData); + if (originalSightData) setSightData(originalSightData); + }, [ originalRouteData, originalStationData, originalSightData, - routeData, - stationData, - sightData, - isRouteLoading, - isStationLoading, - isSightLoading, - ] - ); + routeChanges, + stationChanges, + sightChanges, + ]); - return ( - {children} - ); -} + function setScaleRange(min: number, max: number) { + setRouteChanges((prev) => { + return { ...prev, scale_min: min, scale_max: max }; + }); + } + + function setMapRotation(rotation: number) { + setRouteChanges((prev) => { + return { ...prev, rotate: rotation }; + }); + } + + function setMapCenter(x: number, y: number) { + setRouteChanges((prev) => { + return { ...prev, center_latitude: x, center_longitude: y }; + }); + } + + async function saveChanges() { + await axiosInstance.patch(`/route/${routeId}`, routeData); + await saveStationChanges(); + await saveSightChanges(); + } + + async function saveStationChanges() { + for (const station of stationChanges) { + const response = await axiosInstance.patch( + `/route/${routeId}/station`, + station + ); + } + } + + async function saveSightChanges() { + console.log("sightChanges", sightChanges); + for (const sight of sightChanges) { + const response = await axiosInstance.patch( + `/route/${routeId}/sight`, + sight + ); + } + } + + function setStationOffset(stationId: number, x: number, y: number) { + setStationChanges((prev) => { + let found = prev.find((station) => station.station_id === stationId); + if (found) { + found.offset_x = x; + found.offset_y = y; + + return prev.map((station) => { + if (station.station_id === stationId) { + return found; + } + return station; + }); + } else { + const foundStation = stationData?.find( + (station) => station.id === stationId + ); + if (foundStation) { + return [ + ...prev, + { + station_id: stationId, + offset_x: x, + offset_y: y, + transfers: foundStation.transfers, + }, + ]; + } + return prev; + } + }); + } + + function setSightCoordinates( + sightId: number, + latitude: number, + longitude: number + ) { + setSightChanges((prev) => { + let found = prev.find((sight) => sight.sight_id === sightId); + if (found) { + found.latitude = latitude; + found.longitude = longitude; + + return prev.map((sight) => { + if (sight.sight_id === sightId) { + return found; + } + return sight; + }); + } else { + const foundSight = sightData?.find((sight) => sight.id === sightId); + if (foundSight) { + return [ + ...prev, + { + sight_id: sightId, + latitude, + longitude, + }, + ]; + } + return prev; + } + }); + } + + useEffect(() => { + console.log("sightChanges", sightChanges); + }, [sightChanges]); + + const value = useMemo( + () => ({ + originalRouteData: originalRouteData, + originalStationData: originalStationData, + originalSightData: originalSightData, + routeData: routeData, + stationData: stationData, + sightData: sightData, + isRouteLoading, + isStationLoading, + isSightLoading, + setScaleRange, + setMapRotation, + setMapCenter, + saveChanges, + setStationOffset, + setSightCoordinates, + }), + [ + originalRouteData, + originalStationData, + originalSightData, + routeData, + stationData, + sightData, + isRouteLoading, + isStationLoading, + isSightLoading, + ] + ); + + return ( + + {children} + + ); + } +); export const useMapData = () => { const context = useContext(MapDataContext); diff --git a/src/pages/route-preview/RightSidebar.tsx b/src/pages/route-preview/RightSidebar.tsx index 591e7be..09a7347 100644 --- a/src/pages/route-preview/RightSidebar.tsx +++ b/src/pages/route-preview/RightSidebar.tsx @@ -5,187 +5,228 @@ import { useTransform } from "./TransformContext"; import { coordinatesToLocal, localToCoordinates } from "./utils"; export function RightSidebar() { - const { routeData, setScaleRange, saveChanges, originalRouteData, setMapRotation, setMapCenter } = useMapData(); - const { rotation, position, screenToLocal, screenCenter, rotateToAngle, setTransform } = useTransform(); - const [minScale, setMinScale] = useState(1); - const [maxScale, setMaxScale] = useState(10); - const [localCenter, setLocalCenter] = useState<{x: number, y: number}>({x: 0, y: 0}); - const [rotationDegrees, setRotationDegrees] = useState(0); + const { + routeData, + setScaleRange, + saveChanges, + originalRouteData, + setMapRotation, + setMapCenter, + } = useMapData(); + const { + rotation, + position, + screenToLocal, + screenCenter, + rotateToAngle, + setTransform, + } = useTransform(); + const [minScale, setMinScale] = useState(1); + const [maxScale, setMaxScale] = useState(10); + const [localCenter, setLocalCenter] = useState<{ x: number; y: number }>({ + x: 0, + y: 0, + }); + const [rotationDegrees, setRotationDegrees] = useState(0); - useEffect(() => { - if(originalRouteData) { - setMinScale(originalRouteData.scale_min ?? 1); - setMaxScale(originalRouteData.scale_max ?? 10); - setRotationDegrees(originalRouteData.rotate ?? 0); - setLocalCenter({x: originalRouteData.center_latitude ?? 0, y: originalRouteData.center_longitude ?? 0}); - } - }, [originalRouteData]); + useEffect(() => { + if (originalRouteData) { + setMinScale(originalRouteData.scale_min ?? 1); + setMaxScale(originalRouteData.scale_max ?? 10); + setRotationDegrees(originalRouteData.rotate ?? 0); + setLocalCenter({ + x: originalRouteData.center_latitude ?? 0, + y: originalRouteData.center_longitude ?? 0, + }); + } + }, [originalRouteData]); - useEffect(() => { - if(minScale && maxScale) { - setScaleRange(minScale, maxScale); - } - }, [minScale, maxScale]); + useEffect(() => { + if (minScale && maxScale) { + setScaleRange(minScale, maxScale); + } + }, [minScale, maxScale]); + useEffect(() => { + setRotationDegrees( + ((Math.round((rotation * 180) / Math.PI) % 360) + 360) % 360 + ); + }, [rotation]); + useEffect(() => { + setMapRotation(rotationDegrees); + }, [rotationDegrees]); - useEffect(() => { - setRotationDegrees((Math.round(rotation * 180 / Math.PI) % 360 + 360) % 360); - }, [rotation]); - useEffect(() => { - setMapRotation(rotationDegrees); - }, [rotationDegrees]); + useEffect(() => { + const center = screenCenter ?? { x: 0, y: 0 }; + const localCenter = screenToLocal(center.x, center.y); + const coordinates = localToCoordinates(localCenter.x, localCenter.y); + setLocalCenter({ x: coordinates.latitude, y: coordinates.longitude }); + }, [position]); - useEffect(() => { - const center = screenCenter ?? {x: 0, y: 0}; - const localCenter = screenToLocal(center.x, center.y); - const coordinates = localToCoordinates(localCenter.x, localCenter.y); - setLocalCenter({x: coordinates.latitude, y: coordinates.longitude}); - }, [position]); + useEffect(() => { + setMapCenter(localCenter.x, localCenter.y); + }, [localCenter]); + function setRotationFromDegrees(degrees: number) { + rotateToAngle((degrees * Math.PI) / 180); + } - useEffect(() => { - setMapCenter(localCenter.x, localCenter.y); - }, [localCenter]); + function pan({ x, y }: { x: number; y: number }) { + const coordinates = coordinatesToLocal(x, y); + setTransform(coordinates.x, coordinates.y); + } - function setRotationFromDegrees(degrees: number) { - rotateToAngle(degrees * Math.PI / 180); - } + if (!routeData) { + console.error("routeData is null"); + return null; + } - function pan({x, y}: {x: number, y: number}) { - const coordinates = coordinatesToLocal(x,y); - setTransform(coordinates.x, coordinates.y); - } + return ( + + + Детали о достопримечательностях + - if(!routeData) { - console.error("routeData is null"); - return null; - } + + setMinScale(Number(e.target.value))} + style={{ backgroundColor: "#222", borderRadius: 4 }} + sx={{ + "& .MuiInputLabel-root": { + color: "#fff", + }, + "& .MuiInputBase-input": { + color: "#fff", + }, + }} + slotProps={{ + input: { + min: 0.1, + }, + }} + /> + setMaxScale(Number(e.target.value))} + style={{ backgroundColor: "#222", borderRadius: 4, color: "#fff" }} + sx={{ + "& .MuiInputLabel-root": { + color: "#fff", + }, + "& .MuiInputBase-input": { + color: "#fff", + }, + }} + slotProps={{ + input: { + min: 0.1, + }, + }} + /> + - return ( - - - Детали о достопримечательностях - + { + const value = Number(e.target.value); + if (!isNaN(value)) { + setRotationFromDegrees(value); + } + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.currentTarget.blur(); + } + }} + style={{ backgroundColor: "#222", borderRadius: 4 }} + sx={{ + "& .MuiInputLabel-root": { + color: "#fff", + }, + "& .MuiInputBase-input": { + color: "#fff", + }, + }} + slotProps={{ + input: { + min: 0, + max: 360, + }, + }} + /> - - setMinScale(Number(e.target.value))} - style={{backgroundColor: "#222", borderRadius: 4}} - sx={{ - '& .MuiInputLabel-root.Mui-focused': { - color: "#fff" - } - }} - slotProps={{ - input: { - min: 0.1 - } - }} - /> - setMaxScale(Number(e.target.value))} - style={{backgroundColor: "#222", borderRadius: 4}} - sx={{ - '& .MuiInputLabel-root.Mui-focused': { - color: "#fff" - } - }} - slotProps={{ - input: { - min: 0.1 - } - }} - /> - + + { + setLocalCenter((prev) => ({ ...prev, x: Number(e.target.value) })); + pan({ x: Number(e.target.value), y: localCenter.y }); + }} + style={{ backgroundColor: "#222", borderRadius: 4 }} + sx={{ + "& .MuiInputLabel-root": { + color: "#fff", + }, + "& .MuiInputBase-input": { + color: "#fff", + }, + }} + /> + { + setLocalCenter((prev) => ({ ...prev, y: Number(e.target.value) })); + pan({ x: localCenter.x, y: Number(e.target.value) }); + }} + style={{ backgroundColor: "#222", borderRadius: 4 }} + sx={{ + "& .MuiInputLabel-root": { + color: "#fff", + }, + "& .MuiInputBase-input": { + color: "#fff", + }, + }} + /> + - { - const value = Number(e.target.value); - if (!isNaN(value)) { - setRotationFromDegrees(value); - } - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.currentTarget.blur(); - } - }} - style={{backgroundColor: "#222", borderRadius: 4}} - sx={{ - '& .MuiInputLabel-root.Mui-focused': { - color: "#fff" - } - }} - slotProps={{ - input: { - min: 0, - max: 360 - } - }} - /> - - - { - setLocalCenter(prev => ({...prev, x: Number(e.target.value)})) - pan({x: Number(e.target.value), y: localCenter.y}); - }} - style={{backgroundColor: "#222", borderRadius: 4}} - sx={{ - '& .MuiInputLabel-root.Mui-focused': { - color: "#fff" - } - }} - /> - { - setLocalCenter(prev => ({...prev, y: Number(e.target.value)})) - pan({x: localCenter.x, y: Number(e.target.value)}); - }} - style={{backgroundColor: "#222", borderRadius: 4}} - sx={{ - '& .MuiInputLabel-root.Mui-focused': { - color: "#fff" - } - }} - /> - - - - - ); -} \ No newline at end of file + + + ); +} diff --git a/src/pages/route-preview/TransformContext.tsx b/src/pages/route-preview/TransformContext.tsx index ebce6d2..9e38225 100644 --- a/src/pages/route-preview/TransformContext.tsx +++ b/src/pages/route-preview/TransformContext.tsx @@ -1,150 +1,204 @@ -import { createContext, ReactNode, useContext, useMemo, useState } from "react"; +import { + createContext, + ReactNode, + useContext, + useMemo, + useState, + useCallback, +} from "react"; import { SCALE_FACTOR, UP_SCALE } from "./Constants"; - const TransformContext = createContext<{ - position: { x: number, y: number }, - scale: number, - rotation: number, - screenCenter?: { x: number, y: number }, + position: { x: number; y: number }; + scale: number; + rotation: number; + screenCenter?: { x: number; y: number }; - setPosition: React.Dispatch>, - setScale: React.Dispatch>, - setRotation: React.Dispatch>, - screenToLocal: (x: number, y: number) => { x: number, y: number }, - localToScreen: (x: number, y: number) => { x: number, y: number }, - rotateToAngle: (to: number, fromPosition?: {x: number, y: number}) => void, - setTransform: (latitude: number, longitude: number, rotationDegrees?: number, scale?: number) => void, - setScreenCenter: React.Dispatch> + setPosition: React.Dispatch>; + setScale: React.Dispatch>; + setRotation: React.Dispatch>; + screenToLocal: (x: number, y: number) => { x: number; y: number }; + localToScreen: (x: number, y: number) => { x: number; y: number }; + rotateToAngle: (to: number, fromPosition?: { x: number; y: number }) => void; + setTransform: ( + latitude: number, + longitude: number, + rotationDegrees?: number, + scale?: number + ) => void; + setScreenCenter: React.Dispatch< + React.SetStateAction<{ x: number; y: number } | undefined> + >; }>({ - position: { x: 0, y: 0 }, - scale: 1, - rotation: 0, - screenCenter: undefined, - setPosition: () => {}, - setScale: () => {}, - setRotation: () => {}, - screenToLocal: () => ({ x: 0, y: 0 }), - localToScreen: () => ({ x: 0, y: 0 }), - rotateToAngle: () => {}, - setTransform: () => {}, - setScreenCenter: () => {} + position: { x: 0, y: 0 }, + scale: 1, + rotation: 0, + screenCenter: undefined, + setPosition: () => {}, + setScale: () => {}, + setRotation: () => {}, + screenToLocal: () => ({ x: 0, y: 0 }), + localToScreen: () => ({ x: 0, y: 0 }), + rotateToAngle: () => {}, + setTransform: () => {}, + setScreenCenter: () => {}, }); // Provider component export const TransformProvider = ({ children }: { children: ReactNode }) => { - const [position, setPosition] = useState({ x: 0, y: 0 }); - const [scale, setScale] = useState(1); - const [rotation, setRotation] = useState(0); - const [screenCenter, setScreenCenter] = useState<{x: number, y: number}>(); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [scale, setScale] = useState(1); + const [rotation, setRotation] = useState(0); + const [screenCenter, setScreenCenter] = useState<{ x: number; y: number }>(); - function screenToLocal(screenX: number, screenY: number) { - // Translate point relative to current pan position - const translatedX = (screenX - position.x) / scale; - const translatedY = (screenY - position.y) / scale; + 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 sinRotation = Math.sin(-rotation); - const rotatedX = translatedX * cosRotation - translatedY * sinRotation; - const rotatedY = translatedX * sinRotation + translatedY * cosRotation; + // Rotate point around center + const cosRotation = Math.cos(-rotation); // Negative rotation to reverse transform + const sinRotation = Math.sin(-rotation); + const rotatedX = translatedX * cosRotation - translatedY * sinRotation; + const rotatedY = translatedX * sinRotation + translatedY * cosRotation; - return { - x: rotatedX / UP_SCALE, - y: rotatedY / UP_SCALE - }; - } + return { + x: rotatedX / UP_SCALE, + y: rotatedY / UP_SCALE, + }; + }, + [position.x, position.y, scale, rotation] + ); - // Inverse of screenToLocal - function localToScreen(localX: number, localY: number) { + // Inverse of screenToLocal + const localToScreen = useCallback( + (localX: number, localY: number) => { + const upscaledX = localX * UP_SCALE; + const upscaledY = localY * UP_SCALE; - const upscaledX = localX * UP_SCALE; - const upscaledY = localY * UP_SCALE; + const cosRotation = Math.cos(rotation); + const sinRotation = Math.sin(rotation); + const rotatedX = upscaledX * cosRotation - upscaledY * sinRotation; + const rotatedY = upscaledX * sinRotation + upscaledY * cosRotation; - const cosRotation = Math.cos(rotation); - const sinRotation = Math.sin(rotation); - const rotatedX = upscaledX * cosRotation - upscaledY * sinRotation; - const rotatedY = upscaledX * sinRotation + upscaledY * cosRotation; + const translatedX = rotatedX * scale + position.x; + const translatedY = rotatedY * scale + position.y; - const translatedX = rotatedX*scale + position.x; - const translatedY = rotatedY*scale + position.y; + return { + x: translatedX, + y: translatedY, + }; + }, + [position.x, position.y, scale, rotation] + ); - return { - x: translatedX, - y: translatedY - }; - } - + const rotateToAngle = useCallback( + (to: number, fromPosition?: { x: number; y: number }) => { + const rotationDiff = to - rotation; + const center = screenCenter ?? { x: 0, y: 0 }; + const cosDelta = Math.cos(rotationDiff); + const sinDelta = Math.sin(rotationDiff); - function rotateToAngle(to: number, fromPosition?: {x: number, y: number}) { - setRotation(to); - const rotationDiff = to - rotation; - - const center = screenCenter ?? {x: 0, y: 0}; - const cosDelta = Math.cos(rotationDiff); - const sinDelta = Math.sin(rotationDiff); + const currentFromPosition = fromPosition ?? position; - fromPosition ??= position; + const newPosition = { + x: + center.x * (1 - cosDelta) + + currentFromPosition.x * cosDelta + + (center.y - currentFromPosition.y) * sinDelta, + y: + center.y * (1 - cosDelta) + + currentFromPosition.y * cosDelta + + (currentFromPosition.x - center.x) * sinDelta, + }; - setPosition({ - x: center.x * (1 - cosDelta) + fromPosition.x * cosDelta + (center.y - fromPosition.y) * sinDelta, - y: center.y * (1 - cosDelta) + fromPosition.y * cosDelta + (fromPosition.x - center.x) * sinDelta - }); - } + // Update both rotation and position in a single batch to avoid stale closure + setRotation(to); + setPosition(newPosition); + }, + [rotation, position, screenCenter] + ); - function setTransform(latitude: number, longitude: number, rotationDegrees?: number, useScale ?: number) { - const selectedRotation = rotationDegrees ? (rotationDegrees * Math.PI / 180) : rotation; - const selectedScale = useScale ? useScale/SCALE_FACTOR : scale; - const center = screenCenter ?? {x: 0, y: 0}; - console.log("center", center.x, center.y); - const newPosition = { - x: -latitude * UP_SCALE * selectedScale, - y: -longitude * UP_SCALE * selectedScale - }; + const setTransform = useCallback( + ( + latitude: number, + longitude: number, + rotationDegrees?: number, + useScale?: number + ) => { + const selectedRotation = + rotationDegrees !== undefined + ? (rotationDegrees * Math.PI) / 180 + : rotation; + const selectedScale = + useScale !== undefined ? useScale / SCALE_FACTOR : scale; + const center = screenCenter ?? { x: 0, y: 0 }; - const cos = Math.cos(selectedRotation); - const sin = Math.sin(selectedRotation); - - // Translate point relative to center, rotate, then translate back - const dx = newPosition.x; - const dy = newPosition.y; - newPosition.x = (dx * cos - dy * sin) + center.x; - newPosition.y = (dx * sin + dy * cos) + center.y; + console.log("center", center.x, center.y); - - setPosition(newPosition); - setRotation(selectedRotation); - setScale(selectedScale); - } + const newPosition = { + x: -latitude * UP_SCALE * selectedScale, + y: -longitude * UP_SCALE * selectedScale, + }; - const value = useMemo(() => ({ - position, - scale, - rotation, - screenCenter, - setPosition, - setScale, - setRotation, - rotateToAngle, - screenToLocal, - localToScreen, - setTransform, - setScreenCenter - }), [position, scale, rotation, screenCenter]); + const cosRot = Math.cos(selectedRotation); + const sinRot = Math.sin(selectedRotation); - return ( - - {children} - - ); + // 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); + }, + [rotation, scale, screenCenter] + ); + + const value = useMemo( + () => ({ + position, + scale, + rotation, + screenCenter, + setPosition, + setScale, + setRotation, + rotateToAngle, + screenToLocal, + localToScreen, + setTransform, + setScreenCenter, + }), + [ + position, + scale, + rotation, + screenCenter, + rotateToAngle, + screenToLocal, + localToScreen, + setTransform, + ] + ); + + return ( + + {children} + + ); }; // Custom hook for easy access to transform values export const useTransform = () => { - const context = useContext(TransformContext); - if (!context) { - throw new Error('useTransform must be used within a TransformProvider'); - } - return context; -}; \ No newline at end of file + const context = useContext(TransformContext); + if (!context) { + throw new Error("useTransform must be used within a TransformProvider"); + } + return context; +}; diff --git a/src/pages/route-preview/TravelPath.tsx b/src/pages/route-preview/TravelPath.tsx index 6f3c003..5102bcb 100644 --- a/src/pages/route-preview/TravelPath.tsx +++ b/src/pages/route-preview/TravelPath.tsx @@ -3,37 +3,32 @@ import { useCallback } from "react"; import { PATH_COLOR, PATH_WIDTH } from "./Constants"; import { coordinatesToLocal } from "./utils"; - interface TravelPathProps { - points: {x: number, y: number}[]; + points: { x: number; y: number }[]; } -export function TravelPath({ - points -}: Readonly) { +export function TravelPath({ points }: Readonly) { + const draw = useCallback( + (g: Graphics) => { + g.clear(); + const coordStart = coordinatesToLocal(points[0].x, points[0].y); + g.moveTo(coordStart.x, coordStart.y); + for (let i = 1; i <= points.length - 1; i++) { + const coordinates = coordinatesToLocal(points[i].x, points[i].y); + g.lineTo(coordinates.x, coordinates.y); + } + g.stroke({ + color: PATH_COLOR, + width: PATH_WIDTH, + }); + }, + [points] + ); - const draw = useCallback((g: Graphics) => { - g.clear(); - const coordStart = coordinatesToLocal(points[0].x, points[0].y); - g.moveTo(coordStart.x, coordStart.y); - for (let i = 1; i < points.length - 1; i++) { - const coordinates = coordinatesToLocal(points[i].x, points[i].y); - g.lineTo(coordinates.x, coordinates.y); - } - g.stroke({ - color: PATH_COLOR, - width: PATH_WIDTH - }); - }, [points]); + if (points.length === 0) { + console.error("points is empty"); + return null; + } - if(points.length === 0) { - console.error("points is empty"); - return null; - } - - return ( - - ); -} \ No newline at end of file + return ; +} diff --git a/src/pages/route-preview/Widgets.tsx b/src/pages/route-preview/Widgets.tsx index f9182cc..2c0d966 100644 --- a/src/pages/route-preview/Widgets.tsx +++ b/src/pages/route-preview/Widgets.tsx @@ -1,31 +1,43 @@ import { Stack, Typography } from "@mui/material"; export function Widgets() { - return ( - - - Станция - - - Погода - - - ) + return ( + + + + Станция + + + + + Погода + + + + ); } diff --git a/src/pages/route-preview/index.tsx b/src/pages/route-preview/index.tsx index b81045d..4d8a4ab 100644 --- a/src/pages/route-preview/index.tsx +++ b/src/pages/route-preview/index.tsx @@ -1,18 +1,14 @@ import { useRef, useEffect, useState } from "react"; +import { Application, ApplicationRef, extend } from "@pixi/react"; import { - Application, - ApplicationRef, - extend -} from '@pixi/react'; -import { - Container, - Graphics, - Sprite, - Texture, - TilingSprite, - Text -} from 'pixi.js'; + Container, + Graphics, + Sprite, + Texture, + TilingSprite, + Text, +} from "pixi.js"; import { Stack } from "@mui/material"; import { MapDataProvider, useMapData } from "./MapDataContext"; import { TransformProvider, useTransform } from "./TransformContext"; @@ -25,128 +21,147 @@ import { LeftSidebar } from "./LeftSidebar"; import { RightSidebar } from "./RightSidebar"; import { Widgets } from "./Widgets"; import { coordinatesToLocal } from "./utils"; +import { LanguageSwitch } from "@/components/LanguageSwitch"; extend({ - Container, - Graphics, - Sprite, - Texture, - TilingSprite, - Text + Container, + Graphics, + Sprite, + Texture, + TilingSprite, + Text, }); export const RoutePreview = () => { - return ( - - - - - - - - - - - - - - ); + return ( + + + +
+ +
+ + + + + + +
+
+
+ ); }; - export function RouteMap() { - const { setPosition, screenToLocal, setTransform, screenCenter } = useTransform(); - const { - routeData, stationData, sightData, originalRouteData - } = useMapData(); - const [points, setPoints] = useState<{x: number, y: number}[]>([]); - const [isSetup, setIsSetup] = useState(false); - - const parentRef = useRef(null); - - useEffect(() => { - if (originalRouteData) { - const path = originalRouteData?.path; - const points = path?.map(([x, y]: [number, number]) => ({x: x * UP_SCALE, y: y * UP_SCALE})) ?? []; - setPoints(points); - } - }, [originalRouteData]); + const { setPosition, screenToLocal, setTransform, screenCenter } = + useTransform(); + const { routeData, stationData, sightData, originalRouteData } = useMapData(); + const [points, setPoints] = useState<{ x: number; y: number }[]>([]); + const [isSetup, setIsSetup] = useState(false); - useEffect(() => { - if(isSetup || !screenCenter) { - return; - } + const parentRef = useRef(null); - if ( - originalRouteData?.center_latitude === originalRouteData?.center_longitude && - originalRouteData?.center_latitude === 0 - ) { - if (points.length > 0) { - let boundingBox = { - from: {x: Infinity, y: Infinity}, - to: {x: -Infinity, y: -Infinity} - }; - for (const point of points) { - boundingBox.from.x = Math.min(boundingBox.from.x, point.x); - boundingBox.from.y = Math.min(boundingBox.from.y, point.y); - boundingBox.to.x = Math.max(boundingBox.to.x, point.x); - boundingBox.to.y = Math.max(boundingBox.to.y, point.y); - } - const newCenter = { - x: -(boundingBox.from.x + boundingBox.to.x) / 2, - y: -(boundingBox.from.y + boundingBox.to.y) / 2 - }; - setPosition(newCenter); - setIsSetup(true); - } - } else if ( - originalRouteData?.center_latitude && - originalRouteData?.center_longitude - ) { - const coordinates = coordinatesToLocal(originalRouteData?.center_latitude, originalRouteData?.center_longitude); - - setTransform( - coordinates.x, - coordinates.y, - originalRouteData?.rotate, - originalRouteData?.scale_min - ); - setIsSetup(true); - } - }, [points, originalRouteData?.center_latitude, originalRouteData?.center_longitude, originalRouteData?.rotate, isSetup, screenCenter]); + useEffect(() => { + if (originalRouteData) { + const path = originalRouteData?.path; + const points = + path?.map(([x, y]: [number, number]) => ({ + x: x * UP_SCALE, + y: y * UP_SCALE, + })) ?? []; + setPoints(points); + } + }, [originalRouteData]); + useEffect(() => { + if (isSetup || !screenCenter) { + return; + } - if (!routeData || !stationData || !sightData) { - console.error("routeData, stationData or sightData is null"); - return
Loading...
; - } + if ( + originalRouteData?.center_latitude === + originalRouteData?.center_longitude && + originalRouteData?.center_latitude === 0 + ) { + if (points.length > 0) { + let boundingBox = { + from: { x: Infinity, y: Infinity }, + to: { x: -Infinity, y: -Infinity }, + }; + for (const point of points) { + boundingBox.from.x = Math.min(boundingBox.from.x, point.x); + boundingBox.from.y = Math.min(boundingBox.from.y, point.y); + boundingBox.to.x = Math.max(boundingBox.to.x, point.x); + boundingBox.to.y = Math.max(boundingBox.to.y, point.y); + } + const newCenter = { + x: -(boundingBox.from.x + boundingBox.to.x) / 2, + y: -(boundingBox.from.y + boundingBox.to.y) / 2, + }; + setPosition(newCenter); + setIsSetup(true); + } + } else if ( + originalRouteData?.center_latitude && + originalRouteData?.center_longitude + ) { + const coordinates = coordinatesToLocal( + originalRouteData?.center_latitude, + originalRouteData?.center_longitude + ); + setTransform( + coordinates.x, + coordinates.y, + originalRouteData?.rotate, + originalRouteData?.scale_min + ); + setIsSetup(true); + } + }, [ + points, + originalRouteData?.center_latitude, + originalRouteData?.center_longitude, + originalRouteData?.rotate, + isSetup, + screenCenter, + ]); - return ( -
- - - - {stationData?.map((obj) => ( - - ))} - {sightData?.map((obj, index) => ( - - ))} + if (!routeData || !stationData || !sightData) { + console.error("routeData, stationData or sightData is null"); + return
Loading...
; + } - { - g.clear(); - const localCenter = screenToLocal(0,0); - g.circle(localCenter.x, localCenter.y, 10); - g.fill("#fff"); - }} - /> -
-
-
- ) -} \ No newline at end of file + return ( +
+ + + + {stationData?.map((obj) => ( + + ))} + {sightData?.map((obj, index) => ( + + ))} + + { + g.clear(); + const localCenter = screenToLocal(0, 0); + g.circle(localCenter.x, localCenter.y, 10); + g.fill("#fff"); + }} + /> + + +
+ ); +} diff --git a/src/pages/route/edit.tsx b/src/pages/route/edit.tsx index 5aea5ef..b055b21 100644 --- a/src/pages/route/edit.tsx +++ b/src/pages/route/edit.tsx @@ -393,18 +393,18 @@ export const RouteEdit = observer(() => { parentResource="route" childResource="station" fields={stationFields} - title="станции" + title="остановки" dragAllowed={true} /> - + {/* type="edit" parentId={routeId} parentResource="route" childResource="vehicle" fields={vehicleFields} title="транспортные средства" - /> + /> */} )} diff --git a/src/pages/route/show.tsx b/src/pages/route/show.tsx index 2a844f8..d8c30bc 100644 --- a/src/pages/route/show.tsx +++ b/src/pages/route/show.tsx @@ -80,17 +80,17 @@ export const RouteShow = observer(() => { parentResource="route" childResource="station" fields={stationFields} - title="станции" + title="остановки" /> - + {/* type="show" parentId={record.id} parentResource="route" childResource="vehicle" fields={vehicleFields} title="транспортные средства" - /> + /> */} type="show" @@ -103,10 +103,9 @@ export const RouteShow = observer(() => { )} - - -