2020-05-21 16:46:50 +10:00
|
|
|
import React, { useRef, useEffect, useState, useContext } from "react";
|
2020-04-27 17:29:46 +10:00
|
|
|
import { Box } from "theme-ui";
|
2020-05-21 16:46:50 +10:00
|
|
|
import { useGesture } from "react-use-gesture";
|
|
|
|
|
import ReactResizeDetector from "react-resize-detector";
|
|
|
|
|
import useImage from "use-image";
|
|
|
|
|
import { Stage, Layer, Image } from "react-konva";
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
|
|
|
|
import useDataSource from "../../helpers/useDataSource";
|
|
|
|
|
|
|
|
|
|
import { mapSources as defaultMapSources } from "../../maps";
|
2020-04-29 18:21:44 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
|
|
|
|
import MapStageContext from "../../contexts/MapStageContext";
|
|
|
|
|
import AuthContext from "../../contexts/AuthContext";
|
2020-04-30 22:08:03 +10:00
|
|
|
|
2020-04-30 16:16:12 +10:00
|
|
|
const zoomSpeed = -0.001;
|
2020-04-27 17:29:46 +10:00
|
|
|
const minZoom = 0.1;
|
|
|
|
|
const maxZoom = 5;
|
|
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
function MapInteraction({ map, children, controls }) {
|
|
|
|
|
const mapSource = useDataSource(map, defaultMapSources);
|
|
|
|
|
const [mapSourceImage] = useImage(mapSource);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const [stageWidth, setStageWidth] = useState(1);
|
|
|
|
|
const [stageHeight, setStageHeight] = useState(1);
|
|
|
|
|
const [stageScale, setStageScale] = useState(1);
|
|
|
|
|
const [stageTranslate, setStageTranslate] = useState({ x: 0, y: 0 });
|
|
|
|
|
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const stageWidthRef = useRef(stageWidth);
|
|
|
|
|
const stageHeightRef = useRef(stageHeight);
|
|
|
|
|
const stageScaleRef = useRef(stageScale);
|
|
|
|
|
const stageTranslateRef = useRef(stageTranslate);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2020-05-21 16:46:50 +10:00
|
|
|
if (map) {
|
|
|
|
|
const mapHeight = stageWidthRef.current * (map.height / map.width);
|
|
|
|
|
setStageTranslate({ x: 0, y: -(mapHeight - stageHeightRef.current) / 2 });
|
|
|
|
|
}
|
2020-04-27 17:29:46 +10:00
|
|
|
}, [map]);
|
|
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const bind = useGesture({
|
|
|
|
|
onWheel: ({ delta }) => {
|
|
|
|
|
const newScale = Math.min(
|
|
|
|
|
Math.max(stageScale - delta[1] * zoomSpeed, minZoom),
|
|
|
|
|
maxZoom
|
|
|
|
|
);
|
|
|
|
|
setStageScale(newScale);
|
|
|
|
|
stageScaleRef.current = newScale;
|
|
|
|
|
},
|
|
|
|
|
onDrag: ({ delta }) => {
|
|
|
|
|
if (!preventMapInteraction) {
|
|
|
|
|
const newTranslate = {
|
|
|
|
|
x: stageTranslate.x + delta[0] / stageScale,
|
|
|
|
|
y: stageTranslate.y + delta[1] / stageScale,
|
|
|
|
|
};
|
|
|
|
|
setStageTranslate(newTranslate);
|
|
|
|
|
stageTranslateRef.current = newTranslate;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function handleResize(width, height) {
|
|
|
|
|
setStageWidth(width);
|
|
|
|
|
setStageHeight(height);
|
|
|
|
|
stageWidthRef.current = width;
|
|
|
|
|
stageHeightRef.current = height;
|
|
|
|
|
}
|
2020-04-30 15:45:20 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const containerRef = useRef();
|
|
|
|
|
usePreventOverscroll(containerRef);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const mapWidth = stageWidth;
|
|
|
|
|
const mapHeight = map ? stageWidth * (map.height / map.width) : stageHeight;
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const mapStageRef = useContext(MapStageContext);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const auth = useContext(AuthContext);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const mapInteraction = {
|
|
|
|
|
stageTranslate,
|
|
|
|
|
stageScale,
|
|
|
|
|
stageWidth,
|
|
|
|
|
stageHeight,
|
|
|
|
|
setPreventMapInteraction,
|
|
|
|
|
mapWidth,
|
|
|
|
|
mapHeight,
|
|
|
|
|
};
|
2020-04-27 17:29:46 +10:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Box
|
2020-05-21 16:46:50 +10:00
|
|
|
sx={{ flexGrow: 1, position: "relative" }}
|
|
|
|
|
ref={containerRef}
|
|
|
|
|
{...bind()}
|
2020-04-27 17:29:46 +10:00
|
|
|
className="map"
|
|
|
|
|
>
|
2020-05-21 16:46:50 +10:00
|
|
|
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
|
|
|
|
|
<Stage
|
|
|
|
|
width={stageWidth}
|
|
|
|
|
height={stageHeight}
|
|
|
|
|
scale={{ x: stageScale, y: stageScale }}
|
|
|
|
|
x={stageWidth / 2}
|
|
|
|
|
y={stageHeight / 2}
|
|
|
|
|
offset={{ x: stageWidth / 2, y: stageHeight / 2 }}
|
|
|
|
|
ref={mapStageRef}
|
|
|
|
|
>
|
|
|
|
|
<Layer x={stageTranslate.x} y={stageTranslate.y}>
|
|
|
|
|
<Image
|
|
|
|
|
image={mapSourceImage}
|
|
|
|
|
width={mapWidth}
|
|
|
|
|
height={mapHeight}
|
|
|
|
|
id="mapImage"
|
2020-05-18 21:52:46 +10:00
|
|
|
/>
|
2020-05-21 16:46:50 +10:00
|
|
|
{/* Forward auth context to konva elements */}
|
|
|
|
|
<AuthContext.Provider value={auth}>
|
|
|
|
|
<MapInteractionProvider value={mapInteraction}>
|
|
|
|
|
{children}
|
|
|
|
|
</MapInteractionProvider>
|
|
|
|
|
</AuthContext.Provider>
|
|
|
|
|
</Layer>
|
|
|
|
|
</Stage>
|
|
|
|
|
</ReactResizeDetector>
|
|
|
|
|
<MapInteractionProvider value={mapInteraction}>
|
|
|
|
|
{controls}
|
|
|
|
|
</MapInteractionProvider>
|
2020-04-27 17:29:46 +10:00
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default MapInteraction;
|