Added map control submenus for brush color and erase all

This commit is contained in:
Mitchell McCaffrey 2020-04-20 11:56:56 +10:00
parent 27d3903e66
commit f5d1cdf60f
4 changed files with 189 additions and 75 deletions

View File

@ -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

View File

@ -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>
</>
); );
} }

View File

@ -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 (

View File

@ -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;