2020-06-21 16:29:42 +10:00
|
|
|
import React, { useContext, useState, useEffect } from "react";
|
2020-04-19 13:33:31 +10:00
|
|
|
import shortid from "shortid";
|
2020-05-22 13:47:11 +10:00
|
|
|
import { Group, Line, Rect, Circle } from "react-konva";
|
|
|
|
|
|
|
|
|
|
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
2020-06-21 16:29:42 +10:00
|
|
|
import MapStageContext from "../../contexts/MapStageContext";
|
2020-04-18 18:11:21 +10:00
|
|
|
|
2020-04-28 17:04:31 +10:00
|
|
|
import { compare as comparePoints } from "../../helpers/vector2";
|
2020-04-27 21:39:21 +10:00
|
|
|
import {
|
|
|
|
|
getBrushPositionForTool,
|
|
|
|
|
getDefaultShapeData,
|
|
|
|
|
getUpdatedShapeData,
|
2020-04-28 17:04:31 +10:00
|
|
|
simplifyPoints,
|
2020-05-22 13:47:11 +10:00
|
|
|
getStrokeWidth,
|
2020-04-27 21:39:21 +10:00
|
|
|
} from "../../helpers/drawing";
|
2020-06-21 16:29:42 +10:00
|
|
|
import { getRelativePointerPositionNormalized } from "../../helpers/konva";
|
2020-04-20 11:56:56 +10:00
|
|
|
|
2020-05-22 13:47:11 +10:00
|
|
|
import colors from "../../helpers/colors";
|
2020-04-29 18:21:44 +10:00
|
|
|
|
2020-04-19 13:33:31 +10:00
|
|
|
function MapDrawing({
|
2020-08-07 12:28:50 +10:00
|
|
|
map,
|
2020-04-19 13:33:31 +10:00
|
|
|
shapes,
|
|
|
|
|
onShapeAdd,
|
2020-05-31 12:12:16 +10:00
|
|
|
onShapesRemove,
|
2020-08-04 14:51:31 +10:00
|
|
|
active,
|
|
|
|
|
toolId,
|
|
|
|
|
toolSettings,
|
2020-04-20 15:17:56 +10:00
|
|
|
gridSize,
|
2020-04-19 13:33:31 +10:00
|
|
|
}) {
|
2020-06-24 09:27:20 +10:00
|
|
|
const { stageScale, mapWidth, mapHeight, interactionEmitter } = useContext(
|
|
|
|
|
MapInteractionContext
|
|
|
|
|
);
|
2020-06-21 16:29:42 +10:00
|
|
|
const mapStageRef = useContext(MapStageContext);
|
2020-04-27 21:39:21 +10:00
|
|
|
const [drawingShape, setDrawingShape] = useState(null);
|
2020-05-31 12:12:16 +10:00
|
|
|
const [isBrushDown, setIsBrushDown] = useState(false);
|
|
|
|
|
const [erasingShapes, setErasingShapes] = useState([]);
|
2020-04-19 17:39:26 +10:00
|
|
|
|
2020-08-04 14:51:31 +10:00
|
|
|
const shouldHover = toolSettings.type === "erase";
|
2020-06-21 11:01:03 +10:00
|
|
|
const isBrush =
|
2020-08-04 14:51:31 +10:00
|
|
|
toolSettings.type === "brush" || toolSettings.type === "paint";
|
2020-06-21 11:01:03 +10:00
|
|
|
const isShape =
|
2020-08-04 14:51:31 +10:00
|
|
|
toolSettings.type === "line" ||
|
|
|
|
|
toolSettings.type === "rectangle" ||
|
|
|
|
|
toolSettings.type === "circle" ||
|
|
|
|
|
toolSettings.type === "triangle";
|
2020-04-29 18:21:44 +10:00
|
|
|
|
2020-06-21 16:29:42 +10:00
|
|
|
useEffect(() => {
|
2020-08-04 14:51:31 +10:00
|
|
|
if (!active) {
|
2020-06-21 16:29:42 +10:00
|
|
|
return;
|
2020-05-31 12:12:16 +10:00
|
|
|
}
|
2020-06-21 16:29:42 +10:00
|
|
|
const mapStage = mapStageRef.current;
|
|
|
|
|
|
|
|
|
|
function getBrushPosition() {
|
|
|
|
|
const mapImage = mapStage.findOne("#mapImage");
|
|
|
|
|
return getBrushPositionForTool(
|
2020-08-07 12:28:50 +10:00
|
|
|
map,
|
2020-06-21 16:29:42 +10:00
|
|
|
getRelativePointerPositionNormalized(mapImage),
|
2020-08-04 14:51:31 +10:00
|
|
|
toolId,
|
|
|
|
|
toolSettings,
|
2020-06-21 16:29:42 +10:00
|
|
|
gridSize,
|
|
|
|
|
shapes
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleBrushDown() {
|
|
|
|
|
const brushPosition = getBrushPosition();
|
|
|
|
|
const commonShapeData = {
|
2020-08-04 14:51:31 +10:00
|
|
|
color: toolSettings.color,
|
|
|
|
|
blend: toolSettings.useBlending,
|
2020-06-21 16:29:42 +10:00
|
|
|
id: shortid.generate(),
|
|
|
|
|
};
|
|
|
|
|
if (isBrush) {
|
|
|
|
|
setDrawingShape({
|
|
|
|
|
type: "path",
|
2020-08-04 14:51:31 +10:00
|
|
|
pathType: toolSettings.type === "brush" ? "stroke" : "fill",
|
2020-06-21 16:29:42 +10:00
|
|
|
data: { points: [brushPosition] },
|
2020-08-04 14:51:31 +10:00
|
|
|
strokeWidth: toolSettings.type === "brush" ? 1 : 0,
|
2020-06-21 16:29:42 +10:00
|
|
|
...commonShapeData,
|
|
|
|
|
});
|
|
|
|
|
} else if (isShape) {
|
|
|
|
|
setDrawingShape({
|
|
|
|
|
type: "shape",
|
2020-08-04 14:51:31 +10:00
|
|
|
shapeType: toolSettings.type,
|
|
|
|
|
data: getDefaultShapeData(toolSettings.type, brushPosition),
|
|
|
|
|
strokeWidth: toolSettings.type === "line" ? 1 : 0,
|
2020-06-21 16:29:42 +10:00
|
|
|
...commonShapeData,
|
|
|
|
|
});
|
2020-05-22 13:47:11 +10:00
|
|
|
}
|
2020-06-21 16:29:42 +10:00
|
|
|
setIsBrushDown(true);
|
|
|
|
|
}
|
2020-05-22 13:47:11 +10:00
|
|
|
|
2020-06-21 16:29:42 +10:00
|
|
|
function handleBrushMove() {
|
|
|
|
|
const brushPosition = getBrushPosition();
|
|
|
|
|
if (isBrushDown && drawingShape) {
|
2020-06-21 11:01:03 +10:00
|
|
|
if (isBrush) {
|
2020-05-25 15:07:12 +10:00
|
|
|
setDrawingShape((prevShape) => {
|
|
|
|
|
const prevPoints = prevShape.data.points;
|
|
|
|
|
if (
|
|
|
|
|
comparePoints(
|
|
|
|
|
prevPoints[prevPoints.length - 1],
|
|
|
|
|
brushPosition,
|
|
|
|
|
0.001
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return prevShape;
|
|
|
|
|
}
|
|
|
|
|
const simplified = simplifyPoints(
|
|
|
|
|
[...prevPoints, brushPosition],
|
|
|
|
|
gridSize,
|
|
|
|
|
stageScale
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
...prevShape,
|
|
|
|
|
data: { points: simplified },
|
|
|
|
|
};
|
|
|
|
|
});
|
2020-06-21 11:01:03 +10:00
|
|
|
} else if (isShape) {
|
2020-05-25 15:07:12 +10:00
|
|
|
setDrawingShape((prevShape) => ({
|
2020-04-27 21:39:21 +10:00
|
|
|
...prevShape,
|
2020-05-25 15:07:12 +10:00
|
|
|
data: getUpdatedShapeData(
|
|
|
|
|
prevShape.shapeType,
|
|
|
|
|
prevShape.data,
|
|
|
|
|
brushPosition,
|
|
|
|
|
gridSize
|
|
|
|
|
),
|
|
|
|
|
}));
|
|
|
|
|
}
|
2020-04-27 21:39:21 +10:00
|
|
|
}
|
2020-06-21 16:29:42 +10:00
|
|
|
}
|
2020-04-18 18:11:21 +10:00
|
|
|
|
2020-06-21 16:29:42 +10:00
|
|
|
function handleBrushUp() {
|
|
|
|
|
if (isBrush && drawingShape) {
|
|
|
|
|
if (drawingShape.data.points.length > 1) {
|
2020-05-22 13:47:11 +10:00
|
|
|
onShapeAdd(drawingShape);
|
|
|
|
|
}
|
2020-06-21 16:29:42 +10:00
|
|
|
} else if (isShape && drawingShape) {
|
|
|
|
|
onShapeAdd(drawingShape);
|
2020-04-19 17:39:26 +10:00
|
|
|
}
|
2020-04-27 21:39:21 +10:00
|
|
|
|
2020-06-21 16:29:42 +10:00
|
|
|
if (erasingShapes.length > 0) {
|
|
|
|
|
onShapesRemove(erasingShapes.map((shape) => shape.id));
|
|
|
|
|
setErasingShapes([]);
|
2020-05-25 15:07:12 +10:00
|
|
|
}
|
2020-06-21 16:29:42 +10:00
|
|
|
|
|
|
|
|
setDrawingShape(null);
|
|
|
|
|
setIsBrushDown(false);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 09:27:20 +10:00
|
|
|
interactionEmitter.on("dragStart", handleBrushDown);
|
|
|
|
|
interactionEmitter.on("drag", handleBrushMove);
|
|
|
|
|
interactionEmitter.on("dragEnd", handleBrushUp);
|
2020-06-21 16:29:42 +10:00
|
|
|
|
|
|
|
|
return () => {
|
2020-06-24 09:27:20 +10:00
|
|
|
interactionEmitter.off("dragStart", handleBrushDown);
|
|
|
|
|
interactionEmitter.off("drag", handleBrushMove);
|
|
|
|
|
interactionEmitter.off("dragEnd", handleBrushUp);
|
2020-06-21 16:29:42 +10:00
|
|
|
};
|
2020-08-07 12:28:50 +10:00
|
|
|
});
|
2020-04-27 17:40:36 +10:00
|
|
|
|
2020-05-31 12:12:16 +10:00
|
|
|
function handleShapeOver(shape, isDown) {
|
|
|
|
|
if (shouldHover && isDown) {
|
|
|
|
|
if (erasingShapes.findIndex((s) => s.id === shape.id) === -1) {
|
|
|
|
|
setErasingShapes((prevShapes) => [...prevShapes, shape]);
|
2020-04-19 13:33:31 +10:00
|
|
|
}
|
2020-05-22 13:47:11 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderShape(shape) {
|
|
|
|
|
const defaultProps = {
|
|
|
|
|
key: shape.id,
|
2020-05-31 12:12:16 +10:00
|
|
|
onMouseMove: () => handleShapeOver(shape, isBrushDown),
|
|
|
|
|
onTouchOver: () => handleShapeOver(shape, isBrushDown),
|
|
|
|
|
onMouseDown: () => handleShapeOver(shape, true),
|
|
|
|
|
onTouchStart: () => handleShapeOver(shape, true),
|
2020-05-22 13:47:11 +10:00
|
|
|
fill: colors[shape.color] || shape.color,
|
|
|
|
|
opacity: shape.blend ? 0.5 : 1,
|
2020-06-24 09:27:20 +10:00
|
|
|
id: shape.id,
|
2020-05-22 13:47:11 +10:00
|
|
|
};
|
|
|
|
|
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"
|
2020-08-07 14:05:55 +10:00
|
|
|
lineJoin="round"
|
2020-05-22 13:47:11 +10:00
|
|
|
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}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2020-06-25 08:47:53 +10:00
|
|
|
} else if (shape.shapeType === "line") {
|
|
|
|
|
return (
|
|
|
|
|
<Line
|
|
|
|
|
points={shape.data.points.reduce(
|
|
|
|
|
(acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
|
|
|
|
|
[]
|
|
|
|
|
)}
|
|
|
|
|
strokeWidth={getStrokeWidth(
|
|
|
|
|
shape.strokeWidth,
|
|
|
|
|
gridSize,
|
|
|
|
|
mapWidth,
|
|
|
|
|
mapHeight
|
|
|
|
|
)}
|
|
|
|
|
stroke={colors[shape.color] || shape.color}
|
|
|
|
|
lineCap="round"
|
|
|
|
|
{...defaultProps}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2020-04-18 18:54:13 +10:00
|
|
|
}
|
|
|
|
|
}
|
2020-05-22 13:47:11 +10:00
|
|
|
}
|
2020-04-18 18:54:13 +10:00
|
|
|
|
2020-05-31 12:12:16 +10:00
|
|
|
function renderErasingShape(shape) {
|
|
|
|
|
const eraseShape = {
|
|
|
|
|
...shape,
|
|
|
|
|
color: "#BB99FF",
|
|
|
|
|
};
|
|
|
|
|
return renderShape(eraseShape);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-18 18:11:21 +10:00
|
|
|
return (
|
2020-05-22 13:47:11 +10:00
|
|
|
<Group>
|
|
|
|
|
{shapes.map(renderShape)}
|
|
|
|
|
{drawingShape && renderShape(drawingShape)}
|
2020-05-31 12:12:16 +10:00
|
|
|
{erasingShapes.length > 0 && erasingShapes.map(renderErasingShape)}
|
2020-05-22 13:47:11 +10:00
|
|
|
</Group>
|
2020-04-18 18:11:21 +10:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default MapDrawing;
|