diff --git a/src/components/map/MapEditor.js b/src/components/map/MapEditor.js
index 74d977f..29a970b 100644
--- a/src/components/map/MapEditor.js
+++ b/src/components/map/MapEditor.js
@@ -2,23 +2,13 @@ import React, { useState, useRef, useEffect } from "react";
import { Box } from "theme-ui";
import { Stage, Layer, Image } from "react-konva";
import ReactResizeDetector from "react-resize-detector";
-import useImage from "use-image";
-import { useGesture } from "react-use-gesture";
-import normalizeWheel from "normalize-wheel";
-import useDataSource from "../../helpers/useDataSource";
+import useMapImage from "../../helpers/useMapImage";
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
-
-import { mapSources as defaultMapSources } from "../../maps";
-
-const wheelZoomSpeed = -0.001;
-const touchZoomSpeed = 0.005;
-const minZoom = 0.1;
-const maxZoom = 5;
+import useStageInteraction from "../../helpers/useStageInteraction";
function MapEditor({ map }) {
- const mapSource = useDataSource(map, defaultMapSources);
- const [mapSourceImage] = useImage(mapSource);
+ const [mapImageSource] = useMapImage(map);
const [stageWidth, setStageWidth] = useState(1);
const [stageHeight, setStageHeight] = useState(1);
@@ -38,9 +28,6 @@ function MapEditor({ map }) {
}
const stageTranslateRef = useRef({ x: 0, y: 0 });
- const isInteractingWithCanvas = useRef(false);
- const pinchPreviousDistanceRef = useRef();
- const pinchPreviousOriginRef = useRef();
const mapLayerRef = useRef();
function handleResize(width, height) {
@@ -48,10 +35,11 @@ function MapEditor({ map }) {
setStageHeight(height);
}
+ // Reset map translate and scale
useEffect(() => {
const layer = mapLayerRef.current;
const containerRect = containerRef.current.getBoundingClientRect();
- if (map && layer) {
+ if (layer) {
let newTranslate;
if (stageRatio > mapRatio) {
newTranslate = {
@@ -72,80 +60,14 @@ function MapEditor({ map }) {
setStageScale(1);
}
- }, [map, mapWidth, mapHeight, stageRatio, mapRatio]);
+ }, [map.id, mapWidth, mapHeight, stageRatio, mapRatio]);
- const bind = useGesture({
- onWheelStart: ({ event }) => {
- isInteractingWithCanvas.current =
- event.target === mapLayerRef.current.getCanvas()._canvas;
- },
- onWheel: ({ event }) => {
- event.persist();
- const { pixelY } = normalizeWheel(event);
- if (!isInteractingWithCanvas.current) {
- return;
- }
- const newScale = Math.min(
- Math.max(stageScale + pixelY * wheelZoomSpeed, minZoom),
- maxZoom
- );
- setStageScale(newScale);
- },
- 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 };
- },
- onDragStart: ({ event }) => {
- isInteractingWithCanvas.current =
- event.target === mapLayerRef.current.getCanvas()._canvas;
- },
- onDrag: ({ delta, pinching }) => {
- if (pinching || !isInteractingWithCanvas.current) {
- return;
- }
-
- const [dx, dy] = delta;
- const stageTranslate = stageTranslateRef.current;
- const layer = mapLayerRef.current;
- 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;
- },
- });
+ const bind = useStageInteraction(
+ mapLayerRef.current,
+ stageScale,
+ setStageScale,
+ stageTranslateRef
+ );
const containerRef = useRef();
usePreventOverscroll(containerRef);
@@ -155,7 +77,7 @@ function MapEditor({ map }) {
sx={{
width: "100%",
height: "300px",
- cursor: "pointer",
+ cursor: "move",
touchAction: "none",
outline: "none",
}}
@@ -173,7 +95,7 @@ function MapEditor({ map }) {
offset={{ x: stageWidth / 2, y: stageHeight / 2 }}
>
-
+
diff --git a/src/components/map/MapInteraction.js b/src/components/map/MapInteraction.js
index 7194d93..0e39319 100644
--- a/src/components/map/MapInteraction.js
+++ b/src/components/map/MapInteraction.js
@@ -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({
>
{
+ function updateMapSource() {
+ if (map && map.type === "file" && map.resolutions) {
+ // If quality is set and the quality is available
+ if (map.quality !== "original" && map.resolutions[map.quality]) {
+ setMapSourceMap({
+ ...map.resolutions[map.quality],
+ id: map.id,
+ quality: map.quality,
+ });
+ } else if (!map.file) {
+ // If no file fallback to the highest resolution
+ const resolutionArray = Object.keys(map.resolutions);
+ setMapSourceMap({
+ ...map.resolutions[resolutionArray[resolutionArray.length - 1]],
+ id: map.id,
+ });
+ } else {
+ setMapSourceMap(map);
+ }
+ } else {
+ setMapSourceMap(map);
+ }
+ }
+ if (map && map.id !== mapSourceMap.id) {
+ updateMapSource();
+ } else if (map && map.type === "file") {
+ if (map.file && map.quality !== mapSourceMap.quality) {
+ updateMapSource();
+ }
+ }
+ }, [map, mapSourceMap]);
+
+ 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]);
+
+ return [loadedMapSourceImage, mapSourceImageStatus];
+}
+
+export default useMapImage;
diff --git a/src/helpers/useStageInteraction.js b/src/helpers/useStageInteraction.js
new file mode 100644
index 0000000..395964e
--- /dev/null
+++ b/src/helpers/useStageInteraction.js
@@ -0,0 +1,107 @@
+import { useRef } from "react";
+import { useGesture } from "react-use-gesture";
+import normalizeWheel from "normalize-wheel";
+
+const wheelZoomSpeed = -0.001;
+const touchZoomSpeed = 0.005;
+const minZoom = 0.1;
+const maxZoom = 5;
+
+function useStageInteraction(
+ layer,
+ stageScale,
+ onStageScaleChange,
+ stageTranslateRef,
+ preventInteraction = false,
+ gesture = {}
+) {
+ const isInteractingWithCanvas = useRef(false);
+ const pinchPreviousDistanceRef = useRef();
+ const pinchPreviousOriginRef = useRef();
+
+ const bind = useGesture({
+ ...gesture,
+ onWheelStart: (props) => {
+ const { event } = props;
+ isInteractingWithCanvas.current =
+ event.target === layer.getCanvas()._canvas;
+ gesture.onWheelStart && gesture.onWheelStart(props);
+ },
+ onWheel: (props) => {
+ const { event } = props;
+ event.persist();
+ const { pixelY } = normalizeWheel(event);
+ if (preventInteraction || !isInteractingWithCanvas.current) {
+ return;
+ }
+ const newScale = Math.min(
+ Math.max(stageScale + pixelY * wheelZoomSpeed, minZoom),
+ maxZoom
+ );
+ onStageScaleChange(newScale);
+ gesture.onWheel && gesture.onWheel(props);
+ },
+ onPinch: (props) => {
+ const { da, origin, first } = props;
+ 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
+ );
+ onStageScaleChange(newScale);
+
+ // Apply translate
+ const stageTranslate = stageTranslateRef.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 };
+ gesture.onPinch && gesture.onPinch(props);
+ },
+ onDragStart: (props) => {
+ const { event } = props;
+ isInteractingWithCanvas.current =
+ event.target === layer.getCanvas()._canvas;
+ gesture.onDragStart && gesture.onDragStart(props);
+ },
+ onDrag: (props) => {
+ const { delta, pinching } = props;
+ if (preventInteraction || pinching || !isInteractingWithCanvas.current) {
+ return;
+ }
+
+ const [dx, dy] = delta;
+ const stageTranslate = stageTranslateRef.current;
+ 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;
+ gesture.onDrag && gesture.onDrag(props);
+ },
+ });
+
+ return bind;
+}
+
+export default useStageInteraction;