Added back map drawing
This commit is contained in:
parent
9e01ad1d0e
commit
d26932d17c
@ -1,14 +1,17 @@
|
|||||||
import React, { useState, useContext } from "react";
|
import React, { useState, useContext, useEffect } from "react";
|
||||||
|
|
||||||
import MapControls from "./MapControls";
|
import MapControls from "./MapControls";
|
||||||
|
|
||||||
import MapInteraction from "./MapInteraction";
|
import MapInteraction from "./MapInteraction";
|
||||||
import MapToken from "./MapToken";
|
import MapToken from "./MapToken";
|
||||||
|
import MapDrawing from "./MapDrawing";
|
||||||
|
|
||||||
import TokenDataContext from "../../contexts/TokenDataContext";
|
import TokenDataContext from "../../contexts/TokenDataContext";
|
||||||
import TokenMenu from "../token/TokenMenu";
|
import TokenMenu from "../token/TokenMenu";
|
||||||
import TokenDragOverlay from "../token/TokenDragOverlay";
|
import TokenDragOverlay from "../token/TokenDragOverlay";
|
||||||
|
|
||||||
|
import { omit } from "../../helpers/shared";
|
||||||
|
|
||||||
function Map({
|
function Map({
|
||||||
map,
|
map,
|
||||||
mapState,
|
mapState,
|
||||||
@ -84,6 +87,43 @@ function Map({
|
|||||||
const [mapShapes, setMapShapes] = useState([]);
|
const [mapShapes, setMapShapes] = useState([]);
|
||||||
const [fogShapes, setFogShapes] = useState([]);
|
const [fogShapes, setFogShapes] = useState([]);
|
||||||
|
|
||||||
|
// Replay the draw actions and convert them to shapes for the map drawing
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mapState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function actionsToShapes(actions, actionIndex) {
|
||||||
|
let shapesById = {};
|
||||||
|
for (let i = 0; i <= actionIndex; i++) {
|
||||||
|
const action = actions[i];
|
||||||
|
if (action.type === "add" || action.type === "edit") {
|
||||||
|
for (let shape of action.shapes) {
|
||||||
|
shapesById[shape.id] = shape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (action.type === "remove") {
|
||||||
|
shapesById = omit(shapesById, action.shapeIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.values(shapesById);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMapShapes(
|
||||||
|
actionsToShapes(mapState.mapDrawActions, mapState.mapDrawActionIndex)
|
||||||
|
);
|
||||||
|
setFogShapes(
|
||||||
|
actionsToShapes(mapState.fogDrawActions, mapState.fogDrawActionIndex)
|
||||||
|
);
|
||||||
|
}, [mapState]);
|
||||||
|
|
||||||
|
function handleMapShapeAdd(shape) {
|
||||||
|
onMapDraw({ type: "add", shapes: [shape] });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMapShapeRemove(shapeId) {
|
||||||
|
onMapDraw({ type: "remove", shapeIds: [shapeId] });
|
||||||
|
}
|
||||||
|
|
||||||
const disabledControls = [];
|
const disabledControls = [];
|
||||||
if (!allowMapDrawing) {
|
if (!allowMapDrawing) {
|
||||||
disabledControls.push("brush");
|
disabledControls.push("brush");
|
||||||
@ -182,6 +222,17 @@ function Map({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const mapDrawing = (
|
||||||
|
<MapDrawing
|
||||||
|
shapes={mapShapes}
|
||||||
|
onShapeAdd={handleMapShapeAdd}
|
||||||
|
onShapeRemove={handleMapShapeRemove}
|
||||||
|
selectedToolId={selectedToolId}
|
||||||
|
selectedToolSettings={toolSettings[selectedToolId]}
|
||||||
|
gridSize={gridSizeNormalized}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapInteraction
|
<MapInteraction
|
||||||
map={map}
|
map={map}
|
||||||
@ -192,7 +243,9 @@ function Map({
|
|||||||
{tokenDragOverlay}
|
{tokenDragOverlay}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
selectedToolId={selectedToolId}
|
||||||
>
|
>
|
||||||
|
{mapDrawing}
|
||||||
{mapTokens}
|
{mapTokens}
|
||||||
</MapInteraction>
|
</MapInteraction>
|
||||||
);
|
);
|
||||||
|
@ -1,123 +1,89 @@
|
|||||||
import React, { useRef, useEffect, useState, useContext } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import shortid from "shortid";
|
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 { compare as comparePoints } from "../../helpers/vector2";
|
||||||
import {
|
import {
|
||||||
getBrushPositionForTool,
|
getBrushPositionForTool,
|
||||||
getDefaultShapeData,
|
getDefaultShapeData,
|
||||||
getUpdatedShapeData,
|
getUpdatedShapeData,
|
||||||
isShapeHovered,
|
|
||||||
drawShape,
|
|
||||||
simplifyPoints,
|
simplifyPoints,
|
||||||
getRelativePointerPosition,
|
getStrokeWidth,
|
||||||
} from "../../helpers/drawing";
|
} from "../../helpers/drawing";
|
||||||
|
|
||||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
import colors from "../../helpers/colors";
|
||||||
|
|
||||||
function MapDrawing({
|
function MapDrawing({
|
||||||
width,
|
|
||||||
height,
|
|
||||||
selectedTool,
|
|
||||||
toolSettings,
|
|
||||||
shapes,
|
shapes,
|
||||||
onShapeAdd,
|
onShapeAdd,
|
||||||
onShapeRemove,
|
onShapeRemove,
|
||||||
|
selectedToolId,
|
||||||
|
selectedToolSettings,
|
||||||
gridSize,
|
gridSize,
|
||||||
}) {
|
}) {
|
||||||
const canvasRef = useRef();
|
const {
|
||||||
const containerRef = useRef();
|
stageDragState,
|
||||||
|
mapDragPosition,
|
||||||
const [isPointerDown, setIsPointerDown] = useState(false);
|
stageScale,
|
||||||
|
mapWidth,
|
||||||
|
mapHeight,
|
||||||
|
} = useContext(MapInteractionContext);
|
||||||
const [drawingShape, setDrawingShape] = useState(null);
|
const [drawingShape, setDrawingShape] = useState(null);
|
||||||
const [pointerPosition, setPointerPosition] = useState({ x: -1, y: -1 });
|
|
||||||
|
|
||||||
const shouldHover = selectedTool === "erase";
|
const shouldHover = selectedToolId === "erase";
|
||||||
const isEditing =
|
const isEditing =
|
||||||
selectedTool === "brush" ||
|
selectedToolId === "brush" ||
|
||||||
selectedTool === "shape" ||
|
selectedToolId === "shape" ||
|
||||||
selectedTool === "erase";
|
selectedToolId === "erase";
|
||||||
|
|
||||||
const { scaleRef } = useContext(MapInteractionContext);
|
|
||||||
|
|
||||||
// Reset pointer position when tool changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPointerPosition({ x: -1, y: -1 });
|
|
||||||
}, [selectedTool]);
|
|
||||||
|
|
||||||
function handleStart(event) {
|
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
return;
|
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) {
|
function startShape() {
|
||||||
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);
|
|
||||||
const brushPosition = getBrushPositionForTool(
|
const brushPosition = getBrushPositionForTool(
|
||||||
position,
|
mapDragPosition,
|
||||||
selectedTool,
|
selectedToolId,
|
||||||
toolSettings,
|
selectedToolSettings,
|
||||||
gridSize,
|
gridSize,
|
||||||
shapes
|
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) => {
|
setDrawingShape((prevShape) => {
|
||||||
const prevPoints = prevShape.data.points;
|
const prevPoints = prevShape.data.points;
|
||||||
if (
|
if (
|
||||||
@ -132,14 +98,14 @@ function MapDrawing({
|
|||||||
const simplified = simplifyPoints(
|
const simplified = simplifyPoints(
|
||||||
[...prevPoints, brushPosition],
|
[...prevPoints, brushPosition],
|
||||||
gridSize,
|
gridSize,
|
||||||
scaleRef.current
|
stageScale
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...prevShape,
|
...prevShape,
|
||||||
data: { points: simplified },
|
data: { points: simplified },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else if (selectedTool === "shape") {
|
} else if (selectedToolId === "shape") {
|
||||||
setDrawingShape((prevShape) => ({
|
setDrawingShape((prevShape) => ({
|
||||||
...prevShape,
|
...prevShape,
|
||||||
data: getUpdatedShapeData(
|
data: getUpdatedShapeData(
|
||||||
@ -151,110 +117,145 @@ function MapDrawing({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function handleStop(event) {
|
function endShape() {
|
||||||
if (!isEditing) {
|
if (selectedToolId === "brush" && drawingShape) {
|
||||||
return;
|
if (drawingShape.data.points.length > 1) {
|
||||||
}
|
onShapeAdd(drawingShape);
|
||||||
if (event.touches && event.touches.length !== 0) {
|
}
|
||||||
return;
|
} else if (selectedToolId === "shape" && drawingShape) {
|
||||||
}
|
|
||||||
if (selectedTool === "brush" && drawingShape) {
|
|
||||||
if (drawingShape.data.points.length > 1) {
|
|
||||||
onShapeAdd(drawingShape);
|
onShapeAdd(drawingShape);
|
||||||
}
|
}
|
||||||
} else if (selectedTool === "shape" && drawingShape) {
|
setDrawingShape(null);
|
||||||
onShapeAdd(drawingShape);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedTool === "erase" && hoveredShapeRef.current && isPointerDown) {
|
switch (stageDragState) {
|
||||||
onShapeRemove(hoveredShapeRef.current.id);
|
case "first":
|
||||||
}
|
startShape();
|
||||||
setIsPointerDown(false);
|
return;
|
||||||
setDrawingShape(null);
|
case "dragging":
|
||||||
}
|
continueShape();
|
||||||
|
return;
|
||||||
// Add listeners for draw events on map to allow drawing past the bounds
|
case "last":
|
||||||
// of the container
|
endShape();
|
||||||
useEffect(() => {
|
return;
|
||||||
const map = document.querySelector(".map");
|
default:
|
||||||
map.addEventListener("mousedown", handleStart);
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
shapes,
|
stageDragState,
|
||||||
width,
|
mapDragPosition,
|
||||||
height,
|
selectedToolId,
|
||||||
pointerPosition,
|
selectedToolSettings,
|
||||||
isPointerDown,
|
isEditing,
|
||||||
selectedTool,
|
|
||||||
drawingShape,
|
|
||||||
gridSize,
|
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 (
|
return (
|
||||||
<div
|
<Group>
|
||||||
style={{
|
{shapes.map(renderShape)}
|
||||||
position: "absolute",
|
{drawingShape && renderShape(drawingShape)}
|
||||||
top: 0,
|
</Group>
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
ref={containerRef}
|
|
||||||
>
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
style={{ width: "100%", height: "100%" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ const zoomSpeed = -0.001;
|
|||||||
const minZoom = 0.1;
|
const minZoom = 0.1;
|
||||||
const maxZoom = 5;
|
const maxZoom = 5;
|
||||||
|
|
||||||
function MapInteraction({ map, children, controls }) {
|
function MapInteraction({ map, children, controls, selectedToolId }) {
|
||||||
const mapSource = useDataSource(map, defaultMapSources);
|
const mapSource = useDataSource(map, defaultMapSources);
|
||||||
const [mapSourceImage] = useImage(mapSource);
|
const [mapSourceImage] = useImage(mapSource);
|
||||||
|
|
||||||
@ -26,7 +26,10 @@ function MapInteraction({ map, children, controls }) {
|
|||||||
const [stageHeight, setStageHeight] = useState(1);
|
const [stageHeight, setStageHeight] = useState(1);
|
||||||
const [stageScale, setStageScale] = useState(1);
|
const [stageScale, setStageScale] = useState(1);
|
||||||
const [stageTranslate, setStageTranslate] = useState({ x: 0, y: 0 });
|
const [stageTranslate, setStageTranslate] = useState({ x: 0, y: 0 });
|
||||||
|
// "none" | "first" | "dragging" | "last"
|
||||||
|
const [stageDragState, setStageDragState] = useState("none");
|
||||||
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
||||||
|
const [mapDragPosition, setMapDragPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
const stageWidthRef = useRef(stageWidth);
|
const stageWidthRef = useRef(stageWidth);
|
||||||
const stageHeightRef = useRef(stageHeight);
|
const stageHeightRef = useRef(stageHeight);
|
||||||
@ -40,6 +43,25 @@ function MapInteraction({ map, children, controls }) {
|
|||||||
}
|
}
|
||||||
}, [map]);
|
}, [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 bind = useGesture({
|
const bind = useGesture({
|
||||||
onWheel: ({ delta }) => {
|
onWheel: ({ delta }) => {
|
||||||
const newScale = Math.min(
|
const newScale = Math.min(
|
||||||
@ -49,16 +71,25 @@ function MapInteraction({ map, children, controls }) {
|
|||||||
setStageScale(newScale);
|
setStageScale(newScale);
|
||||||
stageScaleRef.current = newScale;
|
stageScaleRef.current = newScale;
|
||||||
},
|
},
|
||||||
onDrag: ({ delta }) => {
|
onDrag: ({ delta, xy, first, last }) => {
|
||||||
if (!preventMapInteraction) {
|
if (preventMapInteraction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMapDragPosition(getMapDragPosition(xy));
|
||||||
|
setStageDragState(first ? "first" : last ? "last" : "dragging");
|
||||||
|
const [dx, dy] = delta;
|
||||||
|
if (selectedToolId === "pan") {
|
||||||
const newTranslate = {
|
const newTranslate = {
|
||||||
x: stageTranslate.x + delta[0] / stageScale,
|
x: stageTranslate.x + dx / stageScale,
|
||||||
y: stageTranslate.y + delta[1] / stageScale,
|
y: stageTranslate.y + dy / stageScale,
|
||||||
};
|
};
|
||||||
setStageTranslate(newTranslate);
|
setStageTranslate(newTranslate);
|
||||||
stageTranslateRef.current = newTranslate;
|
stageTranslateRef.current = newTranslate;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onDragEnd: () => {
|
||||||
|
setStageDragState("none");
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleResize(width, height) {
|
function handleResize(width, height) {
|
||||||
@ -75,6 +106,7 @@ function MapInteraction({ map, children, controls }) {
|
|||||||
const mapHeight = map ? stageWidth * (map.height / map.width) : stageHeight;
|
const mapHeight = map ? stageWidth * (map.height / map.width) : stageHeight;
|
||||||
|
|
||||||
const mapStageRef = useContext(MapStageContext);
|
const mapStageRef = useContext(MapStageContext);
|
||||||
|
const mapImageRef = useRef();
|
||||||
|
|
||||||
const auth = useContext(AuthContext);
|
const auth = useContext(AuthContext);
|
||||||
|
|
||||||
@ -83,9 +115,11 @@ function MapInteraction({ map, children, controls }) {
|
|||||||
stageScale,
|
stageScale,
|
||||||
stageWidth,
|
stageWidth,
|
||||||
stageHeight,
|
stageHeight,
|
||||||
|
stageDragState,
|
||||||
setPreventMapInteraction,
|
setPreventMapInteraction,
|
||||||
mapWidth,
|
mapWidth,
|
||||||
mapHeight,
|
mapHeight,
|
||||||
|
mapDragPosition,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -111,6 +145,7 @@ function MapInteraction({ map, children, controls }) {
|
|||||||
width={mapWidth}
|
width={mapWidth}
|
||||||
height={mapHeight}
|
height={mapHeight}
|
||||||
id="mapImage"
|
id="mapImage"
|
||||||
|
ref={mapImageRef}
|
||||||
/>
|
/>
|
||||||
{/* Forward auth context to konva elements */}
|
{/* Forward auth context to konva elements */}
|
||||||
<AuthContext.Provider value={auth}>
|
<AuthContext.Provider value={auth}>
|
||||||
|
@ -140,13 +140,13 @@ export function getUpdatedShapeData(type, data, brushPosition, gridSize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultStrokeSize = 1 / 10;
|
const defaultStrokeWidth = 1 / 10;
|
||||||
export function getStrokeSize(multiplier, gridSize, canvasWidth, canvasHeight) {
|
export function getStrokeWidth(multiplier, gridSize, mapWidth, mapHeight) {
|
||||||
const gridPixelSize = Vector2.multiply(gridSize, {
|
const gridPixelSize = Vector2.multiply(gridSize, {
|
||||||
x: canvasWidth,
|
x: mapWidth,
|
||||||
y: canvasHeight,
|
y: mapHeight,
|
||||||
});
|
});
|
||||||
return Vector2.min(gridPixelSize) * defaultStrokeSize * multiplier;
|
return Vector2.min(gridPixelSize) * defaultStrokeWidth * multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shapeHasFill(shape) {
|
export function shapeHasFill(shape) {
|
||||||
@ -330,7 +330,7 @@ export function drawShape(shape, context, gridSize, canvasWidth, canvasHeight) {
|
|||||||
context.strokeStyle = color;
|
context.strokeStyle = color;
|
||||||
if (shape.strokeWidth > 0) {
|
if (shape.strokeWidth > 0) {
|
||||||
context.lineCap = "round";
|
context.lineCap = "round";
|
||||||
context.lineWidth = getStrokeSize(
|
context.lineWidth = getStrokeWidth(
|
||||||
shape.strokeWidth,
|
shape.strokeWidth,
|
||||||
gridSize,
|
gridSize,
|
||||||
canvasWidth,
|
canvasWidth,
|
||||||
|
Loading…
Reference in New Issue
Block a user