Refactored konva map interaction and map image source
This commit is contained in:
@@ -1,17 +1,13 @@
|
||||
import React, { useRef, useEffect, useState, useContext } from "react";
|
||||
import { Box } from "theme-ui";
|
||||
import { useGesture } from "react-use-gesture";
|
||||
import ReactResizeDetector from "react-resize-detector";
|
||||
import useImage from "use-image";
|
||||
import { Stage, Layer, Image } from "react-konva";
|
||||
import { EventEmitter } from "events";
|
||||
import normalizeWheel from "normalize-wheel";
|
||||
|
||||
import useMapImage from "../../helpers/useMapImage";
|
||||
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
||||
import useDataSource from "../../helpers/useDataSource";
|
||||
import useKeyboard from "../../helpers/useKeyboard";
|
||||
|
||||
import { mapSources as defaultMapSources } from "../../maps";
|
||||
import useStageInteraction from "../../helpers/useStageInteraction";
|
||||
|
||||
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
||||
import MapStageContext, {
|
||||
@@ -21,11 +17,6 @@ import AuthContext from "../../contexts/AuthContext";
|
||||
import SettingsContext from "../../contexts/SettingsContext";
|
||||
import KeyboardContext from "../../contexts/KeyboardContext";
|
||||
|
||||
const wheelZoomSpeed = -0.001;
|
||||
const touchZoomSpeed = 0.005;
|
||||
const minZoom = 0.1;
|
||||
const maxZoom = 5;
|
||||
|
||||
function MapInteraction({
|
||||
map,
|
||||
children,
|
||||
@@ -34,29 +25,7 @@ function MapInteraction({
|
||||
onSelectedToolChange,
|
||||
disabledControls,
|
||||
}) {
|
||||
let mapSourceMap = map;
|
||||
if (map && map.type === "file" && map.resolutions) {
|
||||
// Set to the quality if available
|
||||
if (map.quality !== "original" && map.resolutions[map.quality]) {
|
||||
mapSourceMap = map.resolutions[map.quality];
|
||||
} else if (!map.file) {
|
||||
// If no file fallback to the highest resolution
|
||||
for (let resolution in map.resolutions) {
|
||||
mapSourceMap = map.resolutions[resolution];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapSource = useDataSource(mapSourceMap, defaultMapSources);
|
||||
const [mapSourceImage, mapSourceImageStatus] = useImage(mapSource);
|
||||
|
||||
// Create a map source that only updates when the image is fully loaded
|
||||
const [loadedMapSourceImage, setLoadedMapSourceImage] = useState();
|
||||
useEffect(() => {
|
||||
if (mapSourceImageStatus === "loaded") {
|
||||
setLoadedMapSourceImage(mapSourceImage);
|
||||
}
|
||||
}, [mapSourceImage, mapSourceImageStatus]);
|
||||
const [mapImageSource, mapImageSourceStatus] = useMapImage(map);
|
||||
|
||||
// Map loaded taking in to account different resolutions
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
@@ -64,10 +33,10 @@ function MapInteraction({
|
||||
if (map === null) {
|
||||
setMapLoaded(false);
|
||||
}
|
||||
if (mapSourceImageStatus === "loaded") {
|
||||
if (mapImageSourceStatus === "loaded") {
|
||||
setMapLoaded(true);
|
||||
}
|
||||
}, [mapSourceImageStatus, map]);
|
||||
}, [mapImageSourceStatus, map]);
|
||||
|
||||
const [stageWidth, setStageWidth] = useState(1);
|
||||
const [stageHeight, setStageHeight] = useState(1);
|
||||
@@ -100,107 +69,6 @@ function MapInteraction({
|
||||
previousMapIdRef.current = map && map.id;
|
||||
}, [map]);
|
||||
|
||||
const pinchPreviousDistanceRef = useRef();
|
||||
const pinchPreviousOriginRef = useRef();
|
||||
const isInteractingWithCanvas = useRef(false);
|
||||
const previousSelectedToolRef = useRef(selectedToolId);
|
||||
|
||||
const [interactionEmitter] = useState(new EventEmitter());
|
||||
|
||||
const bind = useGesture({
|
||||
onWheelStart: ({ event }) => {
|
||||
isInteractingWithCanvas.current =
|
||||
event.target === mapLayerRef.current.getCanvas()._canvas;
|
||||
},
|
||||
onWheel: ({ event }) => {
|
||||
event.persist();
|
||||
const { pixelY } = normalizeWheel(event);
|
||||
if (preventMapInteraction || !isInteractingWithCanvas.current) {
|
||||
return;
|
||||
}
|
||||
const newScale = Math.min(
|
||||
Math.max(stageScale + pixelY * wheelZoomSpeed, minZoom),
|
||||
maxZoom
|
||||
);
|
||||
setStageScale(newScale);
|
||||
},
|
||||
onPinchStart: () => {
|
||||
// Change to pan tool when pinching and zooming
|
||||
previousSelectedToolRef.current = selectedToolId;
|
||||
onSelectedToolChange("pan");
|
||||
},
|
||||
onPinch: ({ da, origin, first }) => {
|
||||
const [distance] = da;
|
||||
const [originX, originY] = origin;
|
||||
if (first) {
|
||||
pinchPreviousDistanceRef.current = distance;
|
||||
pinchPreviousOriginRef.current = { x: originX, y: originY };
|
||||
}
|
||||
|
||||
// Apply scale
|
||||
const distanceDelta = distance - pinchPreviousDistanceRef.current;
|
||||
const originXDelta = originX - pinchPreviousOriginRef.current.x;
|
||||
const originYDelta = originY - pinchPreviousOriginRef.current.y;
|
||||
const newScale = Math.min(
|
||||
Math.max(stageScale + distanceDelta * touchZoomSpeed, minZoom),
|
||||
maxZoom
|
||||
);
|
||||
setStageScale(newScale);
|
||||
|
||||
// Apply translate
|
||||
const stageTranslate = stageTranslateRef.current;
|
||||
const layer = mapLayerRef.current;
|
||||
const newTranslate = {
|
||||
x: stageTranslate.x + originXDelta / newScale,
|
||||
y: stageTranslate.y + originYDelta / newScale,
|
||||
};
|
||||
layer.x(newTranslate.x);
|
||||
layer.y(newTranslate.y);
|
||||
layer.draw();
|
||||
stageTranslateRef.current = newTranslate;
|
||||
|
||||
pinchPreviousDistanceRef.current = distance;
|
||||
pinchPreviousOriginRef.current = { x: originX, y: originY };
|
||||
},
|
||||
onPinchEnd: () => {
|
||||
onSelectedToolChange(previousSelectedToolRef.current);
|
||||
},
|
||||
onDragStart: ({ event }) => {
|
||||
isInteractingWithCanvas.current =
|
||||
event.target === mapLayerRef.current.getCanvas()._canvas;
|
||||
},
|
||||
onDrag: ({ delta, first, last, pinching }) => {
|
||||
if (
|
||||
preventMapInteraction ||
|
||||
pinching ||
|
||||
!isInteractingWithCanvas.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [dx, dy] = delta;
|
||||
const stageTranslate = stageTranslateRef.current;
|
||||
const layer = mapLayerRef.current;
|
||||
if (selectedToolId === "pan") {
|
||||
const newTranslate = {
|
||||
x: stageTranslate.x + dx / stageScale,
|
||||
y: stageTranslate.y + dy / stageScale,
|
||||
};
|
||||
layer.x(newTranslate.x);
|
||||
layer.y(newTranslate.y);
|
||||
layer.draw();
|
||||
stageTranslateRef.current = newTranslate;
|
||||
}
|
||||
if (first) {
|
||||
interactionEmitter.emit("dragStart");
|
||||
} else if (last) {
|
||||
interactionEmitter.emit("dragEnd");
|
||||
} else {
|
||||
interactionEmitter.emit("drag");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function handleResize(width, height) {
|
||||
setStageWidth(width);
|
||||
setStageHeight(height);
|
||||
@@ -208,6 +76,41 @@ function MapInteraction({
|
||||
stageHeightRef.current = height;
|
||||
}
|
||||
|
||||
const mapStageRef = useContext(MapStageContext);
|
||||
const mapLayerRef = useRef();
|
||||
const mapImageRef = useRef();
|
||||
|
||||
const previousSelectedToolRef = useRef(selectedToolId);
|
||||
|
||||
const [interactionEmitter] = useState(new EventEmitter());
|
||||
|
||||
const bind = useStageInteraction(
|
||||
mapLayerRef.current,
|
||||
stageScale,
|
||||
setStageScale,
|
||||
stageTranslateRef,
|
||||
preventMapInteraction,
|
||||
{
|
||||
onPinchStart: () => {
|
||||
// Change to pan tool when pinching and zooming
|
||||
previousSelectedToolRef.current = selectedToolId;
|
||||
onSelectedToolChange("pan");
|
||||
},
|
||||
onPinchEnd: () => {
|
||||
onSelectedToolChange(previousSelectedToolRef.current);
|
||||
},
|
||||
onDrag: ({ first, last }) => {
|
||||
if (first) {
|
||||
interactionEmitter.emit("dragStart");
|
||||
} else if (last) {
|
||||
interactionEmitter.emit("dragEnd");
|
||||
} else {
|
||||
interactionEmitter.emit("drag");
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
function handleKeyDown(event) {
|
||||
// Change to pan tool when pressing space
|
||||
if (event.key === " " && selectedToolId === "pan") {
|
||||
@@ -272,10 +175,6 @@ function MapInteraction({
|
||||
const mapWidth = stageWidth;
|
||||
const mapHeight = map ? stageWidth * (map.height / map.width) : stageHeight;
|
||||
|
||||
const mapStageRef = useContext(MapStageContext);
|
||||
const mapLayerRef = useRef();
|
||||
const mapImageRef = useRef();
|
||||
|
||||
const auth = useContext(AuthContext);
|
||||
const settings = useContext(SettingsContext);
|
||||
|
||||
@@ -314,7 +213,7 @@ function MapInteraction({
|
||||
>
|
||||
<Layer ref={mapLayerRef}>
|
||||
<Image
|
||||
image={mapLoaded && loadedMapSourceImage}
|
||||
image={mapLoaded && mapImageSource}
|
||||
width={mapWidth}
|
||||
height={mapHeight}
|
||||
id="mapImage"
|
||||
|
||||
Reference in New Issue
Block a user