diff --git a/src/components/map/MapDrawing.js b/src/components/map/MapDrawing.js index e83cac7..935ec4c 100644 --- a/src/components/map/MapDrawing.js +++ b/src/components/map/MapDrawing.js @@ -25,7 +25,9 @@ function MapDrawing({ selectedToolSettings, gridSize, }) { - const { stageScale, mapWidth, mapHeight } = useContext(MapInteractionContext); + const { stageScale, mapWidth, mapHeight, interactionEmitter } = useContext( + MapInteractionContext + ); const mapStageRef = useContext(MapStageContext); const [drawingShape, setDrawingShape] = useState(null); const [isBrushDown, setIsBrushDown] = useState(false); @@ -145,14 +147,14 @@ function MapDrawing({ setIsBrushDown(false); } - mapStage.on("mousedown touchstart", handleBrushDown); - mapStage.on("mousemove touchmove", handleBrushMove); - mapStage.on("mouseup touchend", handleBrushUp); + interactionEmitter.on("dragStart", handleBrushDown); + interactionEmitter.on("drag", handleBrushMove); + interactionEmitter.on("dragEnd", handleBrushUp); return () => { - mapStage.off("mousedown touchstart", handleBrushDown); - mapStage.off("mousemove touchmove", handleBrushMove); - mapStage.off("mouseup touchend", handleBrushUp); + interactionEmitter.off("dragStart", handleBrushDown); + interactionEmitter.off("drag", handleBrushMove); + interactionEmitter.off("dragEnd", handleBrushUp); }; }, [ drawingShape, @@ -169,6 +171,7 @@ function MapDrawing({ selectedToolSettings, shapes, stageScale, + interactionEmitter, ]); function handleShapeOver(shape, isDown) { @@ -188,6 +191,7 @@ function MapDrawing({ onTouchStart: () => handleShapeOver(shape, true), fill: colors[shape.color] || shape.color, opacity: shape.blend ? 0.5 : 1, + id: shape.id, }; if (shape.type === "path") { return ( diff --git a/src/components/map/MapFog.js b/src/components/map/MapFog.js index c09b2ee..563f282 100644 --- a/src/components/map/MapFog.js +++ b/src/components/map/MapFog.js @@ -31,7 +31,9 @@ function MapFog({ selectedToolSettings, gridSize, }) { - const { stageScale, mapWidth, mapHeight } = useContext(MapInteractionContext); + const { stageScale, mapWidth, mapHeight, interactionEmitter } = useContext( + MapInteractionContext + ); const mapStageRef = useContext(MapStageContext); const [drawingShape, setDrawingShape] = useState(null); const [isBrushDown, setIsBrushDown] = useState(false); @@ -109,18 +111,6 @@ function MapFog({ }; }); } - if (selectedToolSettings.type === "polygon" && drawingShape) { - const brushPosition = getBrushPosition(); - setDrawingShape((prevShape) => { - return { - ...prevShape, - data: { - ...prevShape.data, - points: [...prevShape.data.points.slice(0, -1), brushPosition], - }, - }; - }); - } } function handleBrushUp() { @@ -152,29 +142,10 @@ function MapFog({ onShapeAdd(shape); } } - setDrawingShape(null); } - if (selectedToolSettings.type === "polygon") { - const brushPosition = getBrushPosition(); - setDrawingShape({ - type: "fog", - data: { - points: [ - ...(drawingShape ? drawingShape.data.points : [brushPosition]), - brushPosition, - ], - holes: [], - }, - strokeWidth: 0.5, - color: selectedToolSettings.useFogSubtract ? "red" : "black", - blend: false, - id: shortid.generate(), - visible: true, - }); - } - + // Erase if (editingShapes.length > 0) { if (selectedToolSettings.type === "remove") { onShapesRemove(editingShapes.map((shape) => shape.id)); @@ -192,14 +163,69 @@ function MapFog({ setIsBrushDown(false); } - mapStage.on("mousedown touchstart", handleBrushDown); - mapStage.on("mousemove touchmove", handleBrushMove); - mapStage.on("mouseup touchend", handleBrushUp); + function handlePolygonClick() { + if (selectedToolSettings.type === "polygon") { + const brushPosition = getBrushPosition(); + setDrawingShape((prevDrawingShape) => { + if (prevDrawingShape) { + return { + ...prevDrawingShape, + data: { + ...prevDrawingShape.data, + points: [...prevDrawingShape.data.points, brushPosition], + }, + }; + } else { + return { + type: "fog", + data: { + points: [brushPosition, brushPosition], + holes: [], + }, + strokeWidth: 0.5, + color: selectedToolSettings.useFogSubtract ? "red" : "black", + blend: false, + id: shortid.generate(), + visible: true, + }; + } + }); + } + } + + function handlePolygonMove() { + if (selectedToolSettings.type === "polygon" && drawingShape) { + const brushPosition = getBrushPosition(); + setDrawingShape((prevShape) => { + if (!prevShape) { + return; + } + return { + ...prevShape, + data: { + ...prevShape.data, + points: [...prevShape.data.points.slice(0, -1), brushPosition], + }, + }; + }); + } + } + + interactionEmitter.on("dragStart", handleBrushDown); + interactionEmitter.on("drag", handleBrushMove); + interactionEmitter.on("dragEnd", handleBrushUp); + // Use mouse events for polygon and erase to allow for single clicks + mapStage.on("mousedown touchstart", handlePolygonMove); + mapStage.on("mousemove touchmove", handlePolygonMove); + mapStage.on("click tap", handlePolygonClick); return () => { - mapStage.off("mousedown touchstart", handleBrushDown); - mapStage.off("mousemove touchmove", handleBrushMove); - mapStage.off("mouseup touchend", handleBrushUp); + interactionEmitter.off("dragStart", handleBrushDown); + interactionEmitter.off("drag", handleBrushMove); + interactionEmitter.off("dragEnd", handleBrushUp); + mapStage.off("mousedown touchstart", handlePolygonMove); + mapStage.off("mousemove touchmove", handlePolygonMove); + mapStage.off("click tap", handlePolygonClick); }; }, [ mapStageRef, @@ -216,6 +242,7 @@ function MapFog({ selectedToolSettings, shapes, stageScale, + interactionEmitter, ]); const finishDrawingPolygon = useCallback(() => { @@ -317,15 +344,16 @@ function MapFog({ if (shape.data.points.length === 0) { return; } + const isCross = shape.data.points.length < 4; return ( { - // Check that there is enough points after clicking - if (shape.data.points.length < 5) { + cross={isCross} + onClick={(e) => { + e.cancelBubble = true; + if (isCross) { setDrawingShape(null); } else { finishDrawingPolygon(); diff --git a/src/components/map/MapInteraction.js b/src/components/map/MapInteraction.js index 01d6e06..104d825 100644 --- a/src/components/map/MapInteraction.js +++ b/src/components/map/MapInteraction.js @@ -4,6 +4,7 @@ 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 usePreventOverscroll from "../../helpers/usePreventOverscroll"; import useDataSource from "../../helpers/useDataSource"; @@ -34,15 +35,12 @@ function MapInteraction({ const [stageWidth, setStageWidth] = useState(1); const [stageHeight, setStageHeight] = useState(1); const [stageScale, setStageScale] = useState(1); - // "none" | "first" | "dragging" | "last" - const [stageDragState, setStageDragState] = useState("none"); const [preventMapInteraction, setPreventMapInteraction] = useState(false); const stageWidthRef = useRef(stageWidth); const stageHeightRef = useRef(stageHeight); // Avoid state udpates when panning the map by using a ref and updating the konva element directly const stageTranslateRef = useRef({ x: 0, y: 0 }); - const mapDragPositionRef = useRef({ x: 0, y: 0 }); // Reset transform when map changes useEffect(() => { @@ -62,30 +60,13 @@ function MapInteraction({ } }, [map]); - // Convert a client space XY to be normalized to the map image - function getMapDragPosition(xy) { - const [x, y] = xy; - const container = containerRef.current; - const mapImage = mapImageRef.current; - if (container && mapImage) { - const containerRect = container.getBoundingClientRect(); - const mapRect = mapImage.getClientRect(); - - const offsetX = x - containerRect.left - mapRect.x; - const offsetY = y - containerRect.top - mapRect.y; - - const normalizedX = offsetX / mapRect.width; - const normalizedY = offsetY / mapRect.height; - - return { x: normalizedX, y: normalizedY }; - } - } - 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 = @@ -168,15 +149,14 @@ function MapInteraction({ layer.draw(); stageTranslateRef.current = newTranslate; } - mapDragPositionRef.current = getMapDragPosition(xy); - const newDragState = first ? "first" : last ? "last" : "dragging"; - if (stageDragState !== newDragState) { - setStageDragState(newDragState); + if (first) { + interactionEmitter.emit("dragStart"); + } else if (last) { + interactionEmitter.emit("dragEnd"); + } else { + interactionEmitter.emit("drag"); } }, - onDragEnd: () => { - setStageDragState("none"); - }, }); function handleResize(width, height) { @@ -214,11 +194,10 @@ function MapInteraction({ stageScale, stageWidth, stageHeight, - stageDragState, setPreventMapInteraction, mapWidth, mapHeight, - mapDragPositionRef, + interactionEmitter, }; // Enable keyboard interaction for map stage container diff --git a/src/contexts/MapInteractionContext.js b/src/contexts/MapInteractionContext.js index 6b11577..cc8397b 100644 --- a/src/contexts/MapInteractionContext.js +++ b/src/contexts/MapInteractionContext.js @@ -4,11 +4,10 @@ const MapInteractionContext = React.createContext({ stageScale: 1, stageWidth: 1, stageHeight: 1, - stageDragState: "none", setPreventMapInteraction: () => {}, mapWidth: 1, mapHeight: 1, - mapDragPositionRef: { current: undefined }, + interactionEmitter: null, }); export const MapInteractionProvider = MapInteractionContext.Provider;