diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index f198f8c..0d863aa 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -1,9 +1,9 @@ -import {MapLocation} from "../../types/MapLocation.ts"; -import {MapRegion} from "../../types/MapRegion.ts"; +import React, { useEffect, useRef, useState } from "react"; +import { MapLocation } from "../../types/MapLocation.ts"; +import { MapRegion } from "../../types/MapRegion.ts"; import Capital from "./locations/Capital.tsx"; import City from "./locations/City.tsx"; import Poi from "./locations/Poi.tsx"; -import {useState} from "react"; import MapLocationTooltip from "./tooltips/MapLocationTooltip.tsx"; import MapRegionTooltip from "./tooltips/MapRegionTooltip.tsx"; @@ -15,7 +15,9 @@ interface MapProps { pois?: MapLocation[]; } -const Map: React.FC = ({imageUrl, regions = [], cities = [], pois = [], capitals = []}) => { +const Map: React.FC = ({ imageUrl, regions = [], cities = [], pois = [], capitals = [] }) => { + const svgRef = useRef(null); + const gRef = useRef(null); const [tooltip, setTooltip] = useState<{ visible: boolean; x: number; y: number; content: JSX.Element | null }>({ visible: false, x: 0, @@ -23,6 +25,79 @@ const Map: React.FC = ({imageUrl, regions = [], cities = [], pois = [] content: null, }); + const [isDragging, setIsDragging] = useState(false); + const [startX, setStartX] = useState(0); + const [startY, setStartY] = useState(0); + const [translate, setTranslate] = useState({ x: 0, y: 0 }); + const [scale, setScale] = useState(1); + + const dragSensitivity = 2; + + useEffect(() => { + const svgElement = svgRef.current; + const gElement = gRef.current; + + if (svgElement && gElement) { + const handleMouseDown = (e: MouseEvent) => { + setIsDragging(true); + setStartX(e.clientX); + setStartY(e.clientY); + }; + + const handleMouseMove = (e: MouseEvent) => { + if (!isDragging) return; + + // Increase deltaX and deltaY by the drag sensitivity factor + const deltaX = (e.clientX - startX) * dragSensitivity; + const deltaY = (e.clientY - startY) * dragSensitivity; + setTranslate(prev => ({ + x: prev.x + deltaX, + y: prev.y + deltaY, + })); + setStartX(e.clientX); + setStartY(e.clientY); + }; + + const handleMouseUp = () => setIsDragging(false); + + const handleWheel = (e: WheelEvent) => { + e.preventDefault(); + const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; + const newScale = Math.min(Math.max(scale * zoomFactor, 1), 6); + + // If zooming out to the minimum scale, reset translation + if (newScale === 1) { + setTranslate({ x: 0, y: 0 }); + } else { + const svgPoint = svgElement.createSVGPoint(); + svgPoint.x = e.clientX; + svgPoint.y = e.clientY; + const mousePoint = svgPoint.matrixTransform(svgElement.getScreenCTM()?.inverse()); + + const newTranslateX = translate.x - (mousePoint.x - translate.x) * (newScale / scale - 1); + const newTranslateY = translate.y - (mousePoint.y - translate.y) * (newScale / scale - 1); + setTranslate({ x: newTranslateX, y: newTranslateY }); + } + setScale(newScale); + }; + + svgElement.addEventListener("mousedown", handleMouseDown); + svgElement.addEventListener("mousemove", handleMouseMove); + svgElement.addEventListener("mouseup", handleMouseUp); + svgElement.addEventListener("mouseleave", handleMouseUp); + svgElement.addEventListener("wheel", handleWheel); + + return () => { + svgElement.removeEventListener("mousedown", handleMouseDown); + svgElement.removeEventListener("mousemove", handleMouseMove); + svgElement.removeEventListener("mouseup", handleMouseUp); + svgElement.removeEventListener("mouseleave", handleMouseUp); + svgElement.removeEventListener("wheel", handleWheel); + }; + } + }, [isDragging, startX, startY, scale]); + + // Tooltip handling remains unchanged const handleMouseEnterRegion = (event: React.MouseEvent, region: MapRegion) => { setTooltip({ visible: true, @@ -55,50 +130,52 @@ const Map: React.FC = ({imageUrl, regions = [], cities = [], pois = [] return ( <> - - - {regions.map((region, index) => ( - handleMouseEnterRegion(e, region)} - onMouseMove={handleMouseMove} - onMouseLeave={handleMouseLeave} - /> - ))} - {capitals.map((location, index) => ( - handleMouseEnterLocation(e, location)} - onMouseMove={handleMouseMove} - onMouseLeave={handleMouseLeave} - /> - ))} - {cities.map((location, index) => ( - handleMouseEnterLocation(e, location)} - onMouseMove={handleMouseMove} - onMouseLeave={handleMouseLeave} - /> - ))} - {pois.map((location, index) => ( - handleMouseEnterLocation(e, location)} - onMouseMove={handleMouseMove} - onMouseLeave={handleMouseLeave} - /> - ))} + + + + {regions.map((region, index) => ( + handleMouseEnterRegion(e, region)} + onMouseMove={handleMouseMove} + onMouseLeave={handleMouseLeave} + /> + ))} + {capitals.map((location, index) => ( + handleMouseEnterLocation(e, location)} + onMouseMove={handleMouseMove} + onMouseLeave={handleMouseLeave} + /> + ))} + {cities.map((location, index) => ( + handleMouseEnterLocation(e, location)} + onMouseMove={handleMouseMove} + onMouseLeave={handleMouseLeave} + /> + ))} + {pois.map((location, index) => ( + handleMouseEnterLocation(e, location)} + onMouseMove={handleMouseMove} + onMouseLeave={handleMouseLeave} + /> + ))} + - {tooltip.visible && ( + {tooltip.visible && !isDragging && (
= ({imageUrl, regions = [], cities = [], pois = [] )} ); -} +}; export default Map; \ No newline at end of file