Added back map drawing
This commit is contained in:
@@ -1,123 +1,89 @@
|
||||
import React, { useRef, useEffect, useState, useContext } from "react";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import shortid from "shortid";
|
||||
import { Group, Line, Rect, Circle } from "react-konva";
|
||||
|
||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||
|
||||
import { compare as comparePoints } from "../../helpers/vector2";
|
||||
import {
|
||||
getBrushPositionForTool,
|
||||
getDefaultShapeData,
|
||||
getUpdatedShapeData,
|
||||
isShapeHovered,
|
||||
drawShape,
|
||||
simplifyPoints,
|
||||
getRelativePointerPosition,
|
||||
getStrokeWidth,
|
||||
} from "../../helpers/drawing";
|
||||
|
||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||
import colors from "../../helpers/colors";
|
||||
|
||||
function MapDrawing({
|
||||
width,
|
||||
height,
|
||||
selectedTool,
|
||||
toolSettings,
|
||||
shapes,
|
||||
onShapeAdd,
|
||||
onShapeRemove,
|
||||
selectedToolId,
|
||||
selectedToolSettings,
|
||||
gridSize,
|
||||
}) {
|
||||
const canvasRef = useRef();
|
||||
const containerRef = useRef();
|
||||
|
||||
const [isPointerDown, setIsPointerDown] = useState(false);
|
||||
const {
|
||||
stageDragState,
|
||||
mapDragPosition,
|
||||
stageScale,
|
||||
mapWidth,
|
||||
mapHeight,
|
||||
} = useContext(MapInteractionContext);
|
||||
const [drawingShape, setDrawingShape] = useState(null);
|
||||
const [pointerPosition, setPointerPosition] = useState({ x: -1, y: -1 });
|
||||
|
||||
const shouldHover = selectedTool === "erase";
|
||||
const shouldHover = selectedToolId === "erase";
|
||||
const isEditing =
|
||||
selectedTool === "brush" ||
|
||||
selectedTool === "shape" ||
|
||||
selectedTool === "erase";
|
||||
selectedToolId === "brush" ||
|
||||
selectedToolId === "shape" ||
|
||||
selectedToolId === "erase";
|
||||
|
||||
const { scaleRef } = useContext(MapInteractionContext);
|
||||
|
||||
// Reset pointer position when tool changes
|
||||
useEffect(() => {
|
||||
setPointerPosition({ x: -1, y: -1 });
|
||||
}, [selectedTool]);
|
||||
|
||||
function handleStart(event) {
|
||||
if (!isEditing) {
|
||||
return;
|
||||
}
|
||||
if (event.touches && event.touches.length !== 1) {
|
||||
setIsPointerDown(false);
|
||||
setDrawingShape(null);
|
||||
return;
|
||||
}
|
||||
const pointer = event.touches ? event.touches[0] : event;
|
||||
const position = getRelativePointerPosition(pointer, containerRef.current);
|
||||
setPointerPosition(position);
|
||||
setIsPointerDown(true);
|
||||
const brushPosition = getBrushPositionForTool(
|
||||
position,
|
||||
selectedTool,
|
||||
toolSettings,
|
||||
gridSize,
|
||||
shapes
|
||||
);
|
||||
const commonShapeData = {
|
||||
color: toolSettings && toolSettings.color,
|
||||
blend: toolSettings && toolSettings.useBlending,
|
||||
id: shortid.generate(),
|
||||
};
|
||||
if (selectedTool === "brush") {
|
||||
setDrawingShape({
|
||||
type: "path",
|
||||
pathType: toolSettings.type,
|
||||
data: { points: [brushPosition] },
|
||||
strokeWidth: toolSettings.type === "stroke" ? 1 : 0,
|
||||
...commonShapeData,
|
||||
});
|
||||
} else if (selectedTool === "shape") {
|
||||
setDrawingShape({
|
||||
type: "shape",
|
||||
shapeType: toolSettings.type,
|
||||
data: getDefaultShapeData(toolSettings.type, brushPosition),
|
||||
strokeWidth: 0,
|
||||
...commonShapeData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleMove(event) {
|
||||
if (!isEditing) {
|
||||
return;
|
||||
}
|
||||
if (event.touches && event.touches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
const pointer = event.touches ? event.touches[0] : event;
|
||||
// Set pointer position every frame for erase tool and fog
|
||||
if (shouldHover) {
|
||||
const position = getRelativePointerPosition(
|
||||
pointer,
|
||||
containerRef.current
|
||||
);
|
||||
setPointerPosition(position);
|
||||
}
|
||||
if (isPointerDown) {
|
||||
const position = getRelativePointerPosition(
|
||||
pointer,
|
||||
containerRef.current
|
||||
);
|
||||
setPointerPosition(position);
|
||||
function startShape() {
|
||||
const brushPosition = getBrushPositionForTool(
|
||||
position,
|
||||
selectedTool,
|
||||
toolSettings,
|
||||
mapDragPosition,
|
||||
selectedToolId,
|
||||
selectedToolSettings,
|
||||
gridSize,
|
||||
shapes
|
||||
);
|
||||
if (selectedTool === "brush") {
|
||||
const commonShapeData = {
|
||||
color: selectedToolSettings && selectedToolSettings.color,
|
||||
blend: selectedToolSettings && selectedToolSettings.useBlending,
|
||||
id: shortid.generate(),
|
||||
};
|
||||
if (selectedToolId === "brush") {
|
||||
setDrawingShape({
|
||||
type: "path",
|
||||
pathType: selectedToolSettings.type,
|
||||
data: { points: [brushPosition] },
|
||||
strokeWidth: selectedToolSettings.type === "stroke" ? 1 : 0,
|
||||
...commonShapeData,
|
||||
});
|
||||
} else if (selectedToolId === "shape") {
|
||||
setDrawingShape({
|
||||
type: "shape",
|
||||
shapeType: selectedToolSettings.type,
|
||||
data: getDefaultShapeData(selectedToolSettings.type, brushPosition),
|
||||
strokeWidth: 0,
|
||||
...commonShapeData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function continueShape() {
|
||||
const brushPosition = getBrushPositionForTool(
|
||||
mapDragPosition,
|
||||
selectedToolId,
|
||||
selectedToolSettings,
|
||||
gridSize,
|
||||
shapes
|
||||
);
|
||||
if (selectedToolId === "brush") {
|
||||
setDrawingShape((prevShape) => {
|
||||
const prevPoints = prevShape.data.points;
|
||||
if (
|
||||
@@ -132,14 +98,14 @@ function MapDrawing({
|
||||
const simplified = simplifyPoints(
|
||||
[...prevPoints, brushPosition],
|
||||
gridSize,
|
||||
scaleRef.current
|
||||
stageScale
|
||||
);
|
||||
return {
|
||||
...prevShape,
|
||||
data: { points: simplified },
|
||||
};
|
||||
});
|
||||
} else if (selectedTool === "shape") {
|
||||
} else if (selectedToolId === "shape") {
|
||||
setDrawingShape((prevShape) => ({
|
||||
...prevShape,
|
||||
data: getUpdatedShapeData(
|
||||
@@ -151,110 +117,145 @@ function MapDrawing({
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleStop(event) {
|
||||
if (!isEditing) {
|
||||
return;
|
||||
}
|
||||
if (event.touches && event.touches.length !== 0) {
|
||||
return;
|
||||
}
|
||||
if (selectedTool === "brush" && drawingShape) {
|
||||
if (drawingShape.data.points.length > 1) {
|
||||
function endShape() {
|
||||
if (selectedToolId === "brush" && drawingShape) {
|
||||
if (drawingShape.data.points.length > 1) {
|
||||
onShapeAdd(drawingShape);
|
||||
}
|
||||
} else if (selectedToolId === "shape" && drawingShape) {
|
||||
onShapeAdd(drawingShape);
|
||||
}
|
||||
} else if (selectedTool === "shape" && drawingShape) {
|
||||
onShapeAdd(drawingShape);
|
||||
setDrawingShape(null);
|
||||
}
|
||||
|
||||
if (selectedTool === "erase" && hoveredShapeRef.current && isPointerDown) {
|
||||
onShapeRemove(hoveredShapeRef.current.id);
|
||||
}
|
||||
setIsPointerDown(false);
|
||||
setDrawingShape(null);
|
||||
}
|
||||
|
||||
// Add listeners for draw events on map to allow drawing past the bounds
|
||||
// of the container
|
||||
useEffect(() => {
|
||||
const map = document.querySelector(".map");
|
||||
map.addEventListener("mousedown", handleStart);
|
||||
map.addEventListener("mousemove", handleMove);
|
||||
map.addEventListener("mouseup", handleStop);
|
||||
map.addEventListener("touchstart", handleStart);
|
||||
map.addEventListener("touchmove", handleMove);
|
||||
map.addEventListener("touchend", handleStop);
|
||||
|
||||
return () => {
|
||||
map.removeEventListener("mousedown", handleStart);
|
||||
map.removeEventListener("mousemove", handleMove);
|
||||
map.removeEventListener("mouseup", handleStop);
|
||||
map.removeEventListener("touchstart", handleStart);
|
||||
map.removeEventListener("touchmove", handleMove);
|
||||
map.removeEventListener("touchend", handleStop);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Rendering
|
||||
*/
|
||||
const hoveredShapeRef = useRef(null);
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (canvas) {
|
||||
const context = canvas.getContext("2d");
|
||||
|
||||
context.clearRect(0, 0, width, height);
|
||||
let hoveredShape = null;
|
||||
for (let shape of shapes) {
|
||||
if (shouldHover) {
|
||||
if (isShapeHovered(shape, context, pointerPosition, width, height)) {
|
||||
hoveredShape = shape;
|
||||
}
|
||||
}
|
||||
drawShape(shape, context, gridSize, width, height);
|
||||
}
|
||||
if (drawingShape) {
|
||||
drawShape(drawingShape, context, gridSize, width, height);
|
||||
}
|
||||
if (hoveredShape) {
|
||||
const shape = { ...hoveredShape, color: "#BB99FF", blend: true };
|
||||
drawShape(shape, context, gridSize, width, height);
|
||||
}
|
||||
hoveredShapeRef.current = hoveredShape;
|
||||
switch (stageDragState) {
|
||||
case "first":
|
||||
startShape();
|
||||
return;
|
||||
case "dragging":
|
||||
continueShape();
|
||||
return;
|
||||
case "last":
|
||||
endShape();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}, [
|
||||
shapes,
|
||||
width,
|
||||
height,
|
||||
pointerPosition,
|
||||
isPointerDown,
|
||||
selectedTool,
|
||||
drawingShape,
|
||||
stageDragState,
|
||||
mapDragPosition,
|
||||
selectedToolId,
|
||||
selectedToolSettings,
|
||||
isEditing,
|
||||
gridSize,
|
||||
shouldHover,
|
||||
stageScale,
|
||||
onShapeAdd,
|
||||
shapes,
|
||||
drawingShape,
|
||||
]);
|
||||
|
||||
function handleShapeClick(_, shape) {
|
||||
if (selectedToolId === "erase") {
|
||||
onShapeRemove(shape.id);
|
||||
}
|
||||
}
|
||||
|
||||
function handleShapeMouseOver(event, shape) {
|
||||
if (shouldHover) {
|
||||
const path = event.target;
|
||||
const hoverColor = "#BB99FF";
|
||||
path.fill(hoverColor);
|
||||
if (shape.type === "path") {
|
||||
path.stroke(hoverColor);
|
||||
}
|
||||
path.getLayer().draw();
|
||||
}
|
||||
}
|
||||
|
||||
function handleShapeMouseOut(event, shape) {
|
||||
if (shouldHover) {
|
||||
const path = event.target;
|
||||
const color = colors[shape.color] || shape.color;
|
||||
path.fill(color);
|
||||
if (shape.type === "path") {
|
||||
path.stroke(color);
|
||||
}
|
||||
path.getLayer().draw();
|
||||
}
|
||||
}
|
||||
|
||||
function renderShape(shape) {
|
||||
const defaultProps = {
|
||||
key: shape.id,
|
||||
onMouseOver: (e) => handleShapeMouseOver(e, shape),
|
||||
onMouseOut: (e) => handleShapeMouseOut(e, shape),
|
||||
onClick: (e) => handleShapeClick(e, shape),
|
||||
fill: colors[shape.color] || shape.color,
|
||||
opacity: shape.blend ? 0.5 : 1,
|
||||
};
|
||||
if (shape.type === "path") {
|
||||
return (
|
||||
<Line
|
||||
points={shape.data.points.reduce(
|
||||
(acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
|
||||
[]
|
||||
)}
|
||||
stroke={colors[shape.color] || shape.color}
|
||||
tension={0.5}
|
||||
closed={shape.pathType === "fill"}
|
||||
fillEnabled={shape.pathType === "fill"}
|
||||
lineCap="round"
|
||||
strokeWidth={getStrokeWidth(
|
||||
shape.strokeWidth,
|
||||
gridSize,
|
||||
mapWidth,
|
||||
mapHeight
|
||||
)}
|
||||
{...defaultProps}
|
||||
/>
|
||||
);
|
||||
} else if (shape.type === "shape") {
|
||||
if (shape.shapeType === "rectangle") {
|
||||
return (
|
||||
<Rect
|
||||
x={shape.data.x * mapWidth}
|
||||
y={shape.data.y * mapHeight}
|
||||
width={shape.data.width * mapWidth}
|
||||
height={shape.data.height * mapHeight}
|
||||
{...defaultProps}
|
||||
/>
|
||||
);
|
||||
} else if (shape.shapeType === "circle") {
|
||||
const minSide = mapWidth < mapHeight ? mapWidth : mapHeight;
|
||||
return (
|
||||
<Circle
|
||||
x={shape.data.x * mapWidth}
|
||||
y={shape.data.y * mapHeight}
|
||||
radius={shape.data.radius * minSide}
|
||||
{...defaultProps}
|
||||
/>
|
||||
);
|
||||
} else if (shape.shapeType === "triangle") {
|
||||
return (
|
||||
<Line
|
||||
points={shape.data.points.reduce(
|
||||
(acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
|
||||
[]
|
||||
)}
|
||||
closed={true}
|
||||
{...defaultProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
ref={containerRef}
|
||||
>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
width={width}
|
||||
height={height}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
<Group>
|
||||
{shapes.map(renderShape)}
|
||||
{drawingShape && renderShape(drawingShape)}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user