Added map control submenus for brush color and erase all
This commit is contained in:
parent
27d3903e66
commit
f5d1cdf60f
@ -43,14 +43,19 @@ function Map({
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const [selectedTool, setSelectedTool] = useState("pan");
|
const [selectedTool, setSelectedTool] = useState("pan");
|
||||||
|
const [brushColor, setBrushColor] = useState("black");
|
||||||
|
|
||||||
const [drawnShapes, setDrawnShapes] = useState([]);
|
const [drawnShapes, setDrawnShapes] = useState([]);
|
||||||
function handleShapeAdd(shape) {
|
function handleShapeAdd(shape) {
|
||||||
onMapDraw({ type: "add", shape });
|
onMapDraw({ type: "add", shapes: [shape] });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleShapeRemove(shapeId) {
|
function handleShapeRemove(shapeId) {
|
||||||
onMapDraw({ type: "remove", shapeId });
|
onMapDraw({ type: "remove", shapeIds: [shapeId] });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShapeRemoveAll() {
|
||||||
|
onMapDraw({ type: "remove", shapeIds: drawnShapes.map((s) => s.id) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replay the draw actions and convert them to shapes for the map drawing
|
// Replay the draw actions and convert them to shapes for the map drawing
|
||||||
@ -59,10 +64,12 @@ function Map({
|
|||||||
for (let i = 0; i <= drawActionIndex; i++) {
|
for (let i = 0; i <= drawActionIndex; i++) {
|
||||||
const action = drawActions[i];
|
const action = drawActions[i];
|
||||||
if (action.type === "add") {
|
if (action.type === "add") {
|
||||||
shapesById[action.shape.id] = action.shape;
|
for (let shape of action.shapes) {
|
||||||
|
shapesById[shape.id] = shape;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (action.type === "remove") {
|
if (action.type === "remove") {
|
||||||
shapesById = omit(shapesById, [action.shapeId]);
|
shapesById = omit(shapesById, action.shapeIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDrawnShapes(Object.values(shapesById));
|
setDrawnShapes(Object.values(shapesById));
|
||||||
@ -267,6 +274,7 @@ function Map({
|
|||||||
shapes={drawnShapes}
|
shapes={drawnShapes}
|
||||||
onShapeAdd={handleShapeAdd}
|
onShapeAdd={handleShapeAdd}
|
||||||
onShapeRemove={handleShapeRemove}
|
onShapeRemove={handleShapeRemove}
|
||||||
|
brushColor={brushColor}
|
||||||
/>
|
/>
|
||||||
{mapTokens}
|
{mapTokens}
|
||||||
</Box>
|
</Box>
|
||||||
@ -280,6 +288,9 @@ function Map({
|
|||||||
onRedo={onMapDrawRedo}
|
onRedo={onMapDrawRedo}
|
||||||
undoDisabled={drawActionIndex < 0}
|
undoDisabled={drawActionIndex < 0}
|
||||||
redoDisabled={drawActionIndex === drawActions.length - 1}
|
redoDisabled={drawActionIndex === drawActions.length - 1}
|
||||||
|
brushColor={brushColor}
|
||||||
|
onBrushColorChange={setBrushColor}
|
||||||
|
onEraseAll={handleShapeRemoveAll}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<ProxyToken
|
<ProxyToken
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Flex, Box, IconButton } from "theme-ui";
|
import { Flex, Box, IconButton, Button } from "theme-ui";
|
||||||
|
|
||||||
import AddMapButton from "./AddMapButton";
|
import AddMapButton from "./AddMapButton";
|
||||||
import ExpandMoreIcon from "../icons/ExpandMoreIcon";
|
import ExpandMoreIcon from "../icons/ExpandMoreIcon";
|
||||||
@ -9,6 +9,10 @@ import EraseToolIcon from "../icons/EraseToolIcon";
|
|||||||
import UndoIcon from "../icons/UndoIcon";
|
import UndoIcon from "../icons/UndoIcon";
|
||||||
import RedoIcon from "../icons/RedoIcon";
|
import RedoIcon from "../icons/RedoIcon";
|
||||||
|
|
||||||
|
import colors, { colorOptions } from "../helpers/colors";
|
||||||
|
|
||||||
|
import MapMenu from "./MapMenu";
|
||||||
|
|
||||||
function MapControls({
|
function MapControls({
|
||||||
onMapChange,
|
onMapChange,
|
||||||
onToolChange,
|
onToolChange,
|
||||||
@ -18,9 +22,85 @@ function MapControls({
|
|||||||
onRedo,
|
onRedo,
|
||||||
undoDisabled,
|
undoDisabled,
|
||||||
redoDisabled,
|
redoDisabled,
|
||||||
|
brushColor,
|
||||||
|
onBrushColorChange,
|
||||||
|
onEraseAll,
|
||||||
}) {
|
}) {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
|
const subMenus = {
|
||||||
|
brush: (
|
||||||
|
<Box sx={{ width: "104px" }} p={1}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{colorOptions.map((color) => (
|
||||||
|
<Box
|
||||||
|
key={color}
|
||||||
|
sx={{
|
||||||
|
width: "25%",
|
||||||
|
paddingTop: "25%",
|
||||||
|
borderRadius: "50%",
|
||||||
|
transform: "scale(0.75)",
|
||||||
|
backgroundColor: colors[color],
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => onBrushColorChange(color)}
|
||||||
|
>
|
||||||
|
{brushColor === color && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
border: "2px solid white",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
borderRadius: "50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
erase: (
|
||||||
|
<Box p={1}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
onEraseAll();
|
||||||
|
setCurrentSubmenu({});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Erase All
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const [currentSubmenu, setCurrentSubmenu] = useState(null);
|
||||||
|
const [currentSubmenuOptions, setCurrentSubmenuOptions] = useState({});
|
||||||
|
|
||||||
|
function handleToolClick(event, tool) {
|
||||||
|
if (tool !== selectedTool) {
|
||||||
|
onToolChange(tool);
|
||||||
|
} else if (subMenus[tool]) {
|
||||||
|
const toolRect = event.target.getBoundingClientRect();
|
||||||
|
setCurrentSubmenu(tool);
|
||||||
|
setCurrentSubmenuOptions({
|
||||||
|
// Align the right of the submenu to the left of the tool and center vertically
|
||||||
|
left: `${toolRect.left - 4}px`,
|
||||||
|
top: `${toolRect.bottom - toolRect.height / 2}px`,
|
||||||
|
style: { transform: "translate(-100%, -50%)" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const divider = (
|
const divider = (
|
||||||
<Box
|
<Box
|
||||||
my={2}
|
my={2}
|
||||||
@ -29,82 +109,94 @@ function MapControls({
|
|||||||
></Box>
|
></Box>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Flex
|
<>
|
||||||
p={2}
|
<Flex
|
||||||
sx={{
|
p={2}
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
aria-label={isExpanded ? "Hide Map Controls" : "Show Map Controls"}
|
|
||||||
title={isExpanded ? "Hide Map Controls" : "Show Map Controls"}
|
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
|
||||||
sx={{
|
|
||||||
transform: `rotate(${isExpanded ? "0" : "180deg"})`,
|
|
||||||
display: "block",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ExpandMoreIcon />
|
|
||||||
</IconButton>
|
|
||||||
<Box
|
|
||||||
sx={{
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
display: isExpanded ? "flex" : "none",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AddMapButton onMapChange={onMapChange} />
|
|
||||||
{divider}
|
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Pan Tool"
|
aria-label={isExpanded ? "Hide Map Controls" : "Show Map Controls"}
|
||||||
title="Pan Tool"
|
title={isExpanded ? "Hide Map Controls" : "Show Map Controls"}
|
||||||
onClick={() => onToolChange("pan")}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
sx={{ color: selectedTool === "pan" ? "primary" : "text" }}
|
sx={{
|
||||||
disabled={disabledTools.includes("pan")}
|
transform: `rotate(${isExpanded ? "0" : "180deg"})`,
|
||||||
|
display: "block",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<PanToolIcon />
|
<ExpandMoreIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<Box
|
||||||
aria-label="Brush Tool"
|
sx={{
|
||||||
title="Brush Tool"
|
flexDirection: "column",
|
||||||
onClick={() => onToolChange("brush")}
|
alignItems: "center",
|
||||||
sx={{ color: selectedTool === "brush" ? "primary" : "text" }}
|
display: isExpanded ? "flex" : "none",
|
||||||
disabled={disabledTools.includes("brush")}
|
}}
|
||||||
>
|
>
|
||||||
<BrushToolIcon />
|
<AddMapButton onMapChange={onMapChange} />
|
||||||
</IconButton>
|
{divider}
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Erase Tool"
|
aria-label="Pan Tool"
|
||||||
title="Erase Tool"
|
title="Pan Tool"
|
||||||
onClick={() => onToolChange("erase")}
|
onClick={(e) => handleToolClick(e, "pan")}
|
||||||
sx={{ color: selectedTool === "erase" ? "primary" : "text" }}
|
sx={{ color: selectedTool === "pan" ? "primary" : "text" }}
|
||||||
disabled={disabledTools.includes("erase")}
|
disabled={disabledTools.includes("pan")}
|
||||||
>
|
>
|
||||||
<EraseToolIcon />
|
<PanToolIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{divider}
|
<IconButton
|
||||||
<IconButton
|
aria-label="Brush Tool"
|
||||||
aria-label="Undo"
|
title="Brush Tool"
|
||||||
title="Undo"
|
onClick={(e) => handleToolClick(e, "brush")}
|
||||||
onClick={() => onUndo()}
|
sx={{ color: selectedTool === "brush" ? "primary" : "text" }}
|
||||||
disabled={undoDisabled}
|
disabled={disabledTools.includes("brush")}
|
||||||
>
|
>
|
||||||
<UndoIcon />
|
<BrushToolIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Redo"
|
aria-label="Erase Tool"
|
||||||
title="Redo"
|
title="Erase Tool"
|
||||||
onClick={() => onRedo()}
|
onClick={(e) => handleToolClick(e, "erase")}
|
||||||
disabled={redoDisabled}
|
sx={{ color: selectedTool === "erase" ? "primary" : "text" }}
|
||||||
>
|
disabled={disabledTools.includes("erase")}
|
||||||
<RedoIcon />
|
>
|
||||||
</IconButton>
|
<EraseToolIcon />
|
||||||
</Box>
|
</IconButton>
|
||||||
</Flex>
|
{divider}
|
||||||
|
<IconButton
|
||||||
|
aria-label="Undo"
|
||||||
|
title="Undo"
|
||||||
|
onClick={() => onUndo()}
|
||||||
|
disabled={undoDisabled}
|
||||||
|
>
|
||||||
|
<UndoIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Redo"
|
||||||
|
title="Redo"
|
||||||
|
onClick={() => onRedo()}
|
||||||
|
disabled={redoDisabled}
|
||||||
|
>
|
||||||
|
<RedoIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<MapMenu
|
||||||
|
isOpen={!!currentSubmenu}
|
||||||
|
onRequestClose={() => {
|
||||||
|
setCurrentSubmenu(null);
|
||||||
|
setCurrentSubmenuOptions({});
|
||||||
|
}}
|
||||||
|
{...currentSubmenuOptions}
|
||||||
|
>
|
||||||
|
{currentSubmenu && subMenus[currentSubmenu]}
|
||||||
|
</MapMenu>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ import React, { useRef, useEffect, useState } from "react";
|
|||||||
import simplify from "simplify-js";
|
import simplify from "simplify-js";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
|
||||||
|
import colors from "../helpers/colors";
|
||||||
|
|
||||||
function MapDrawing({
|
function MapDrawing({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -9,6 +11,7 @@ function MapDrawing({
|
|||||||
shapes,
|
shapes,
|
||||||
onShapeAdd,
|
onShapeAdd,
|
||||||
onShapeRemove,
|
onShapeRemove,
|
||||||
|
brushColor,
|
||||||
}) {
|
}) {
|
||||||
const canvasRef = useRef();
|
const canvasRef = useRef();
|
||||||
const containerRef = useRef();
|
const containerRef = useRef();
|
||||||
@ -70,7 +73,11 @@ function MapDrawing({
|
|||||||
if (selectedTool === "brush") {
|
if (selectedTool === "brush") {
|
||||||
if (brushPoints.length > 0) {
|
if (brushPoints.length > 0) {
|
||||||
const simplifiedPoints = simplify(brushPoints, 0.001);
|
const simplifiedPoints = simplify(brushPoints, 0.001);
|
||||||
onShapeAdd({ id: shortid.generate(), points: simplifiedPoints });
|
onShapeAdd({
|
||||||
|
id: shortid.generate(),
|
||||||
|
points: simplifiedPoints,
|
||||||
|
color: brushColor,
|
||||||
|
});
|
||||||
setBrushPoints([]);
|
setBrushPoints([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,11 +125,11 @@ function MapDrawing({
|
|||||||
hoveredShape = shape;
|
hoveredShape = shape;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawPath(path, "#000000", context);
|
drawPath(path, colors[shape.color], context);
|
||||||
}
|
}
|
||||||
if (selectedTool === "brush" && brushPoints.length > 0) {
|
if (selectedTool === "brush" && brushPoints.length > 0) {
|
||||||
const path = pointsToPath(brushPoints);
|
const path = pointsToPath(brushPoints);
|
||||||
drawPath(path, "#000000", context);
|
drawPath(path, colors[brushColor], context);
|
||||||
}
|
}
|
||||||
if (hoveredShape) {
|
if (hoveredShape) {
|
||||||
const path = pointsToPath(hoveredShape.points);
|
const path = pointsToPath(hoveredShape.points);
|
||||||
@ -138,6 +145,7 @@ function MapDrawing({
|
|||||||
isDrawing,
|
isDrawing,
|
||||||
selectedTool,
|
selectedTool,
|
||||||
brushPoints,
|
brushPoints,
|
||||||
|
brushColor,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -10,6 +10,7 @@ function MapMenu({
|
|||||||
bottom,
|
bottom,
|
||||||
right,
|
right,
|
||||||
children,
|
children,
|
||||||
|
style,
|
||||||
}) {
|
}) {
|
||||||
function handleModalContent(node) {
|
function handleModalContent(node) {
|
||||||
if (node) {
|
if (node) {
|
||||||
@ -49,6 +50,7 @@ function MapMenu({
|
|||||||
padding: 0,
|
padding: 0,
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
border: "none",
|
border: "none",
|
||||||
|
...style,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
contentRef={handleModalContent}
|
contentRef={handleModalContent}
|
||||||
@ -64,6 +66,7 @@ MapMenu.defaultProps = {
|
|||||||
left: "initial",
|
left: "initial",
|
||||||
right: "initial",
|
right: "initial",
|
||||||
bottom: "initial",
|
bottom: "initial",
|
||||||
|
style: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MapMenu;
|
export default MapMenu;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user