Moved undo and redo into tool settings and implemented it for fog

This commit is contained in:
Mitchell McCaffrey 2020-04-30 09:29:16 +10:00
parent ccaa51fe84
commit 88b4785307
9 changed files with 202 additions and 102 deletions

View File

@ -26,9 +26,11 @@ function Map({
onMapChange, onMapChange,
onMapStateChange, onMapStateChange,
onMapDraw, onMapDraw,
onMapDrawUndo,
onMapDrawRedo,
onFogDraw, onFogDraw,
onMapUndo, onFogDrawUndo,
onMapRedo, onFogDrawRedo,
allowMapDrawing, allowMapDrawing,
allowFogDrawing, allowFogDrawing,
allowTokenChange, allowTokenChange,
@ -82,28 +84,40 @@ function Map({
timestamp: Date.now(), timestamp: Date.now(),
}); });
} }
if (action === "mapUndo") {
onMapDrawUndo();
}
if (action === "mapRedo") {
onMapDrawRedo();
}
if (action === "fogUndo") {
onFogDrawUndo();
}
if (action === "fogRedo") {
onFogDrawRedo();
}
} }
const [mapShapes, setMapShapes] = useState([]); const [mapShapes, setMapShapes] = useState([]);
function handleMapShapeAdd(shape) { function handleMapShapeAdd(shape) {
onMapDraw({ type: "add", shapes: [shape], timestamp: Date.now() }); onMapDraw({ type: "add", shapes: [shape] });
} }
function handleMapShapeRemove(shapeId) { function handleMapShapeRemove(shapeId) {
onMapDraw({ type: "remove", shapeIds: [shapeId], timestamp: Date.now() }); onMapDraw({ type: "remove", shapeIds: [shapeId] });
} }
const [fogShapes, setFogShapes] = useState([]); const [fogShapes, setFogShapes] = useState([]);
function handleFogShapeAdd(shape) { function handleFogShapeAdd(shape) {
onFogDraw({ type: "add", shapes: [shape], timestamp: Date.now() }); onFogDraw({ type: "add", shapes: [shape] });
} }
function handleFogShapeRemove(shapeId) { function handleFogShapeRemove(shapeId) {
onFogDraw({ type: "remove", shapeIds: [shapeId], timestamp: Date.now() }); onFogDraw({ type: "remove", shapeIds: [shapeId] });
} }
function handleFogShapeEdit(shape) { function handleFogShapeEdit(shape) {
onFogDraw({ type: "edit", shapes: [shape], timestamp: Date.now() }); onFogDraw({ type: "edit", shapes: [shape] });
} }
// 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
@ -141,28 +155,38 @@ function Map({
disabledControls.push("shape"); disabledControls.push("shape");
disabledControls.push("erase"); disabledControls.push("erase");
} }
// If no actions that can be undone
if (!allowFogDrawing && !allowMapDrawing) {
disabledControls.push("undo");
disabledControls.push("redo");
}
if (!map) { if (!map) {
disabledControls.push("pan"); disabledControls.push("pan");
} }
if (mapShapes.length === 0) { if (mapShapes.length === 0) {
disabledControls.push("erase"); disabledControls.push("erase");
} }
if (!mapState || mapState.mapDrawActionIndex < 0) {
disabledControls.push("undo");
}
if (!allowFogDrawing) { if (!allowFogDrawing) {
disabledControls.push("fog"); disabledControls.push("fog");
} }
const disabledSettings = { fog: [], brush: [], shape: [], erase: [] };
if (!mapState || mapState.mapDrawActionIndex < 0) {
disabledSettings.brush.push("undo");
disabledSettings.shape.push("undo");
disabledSettings.erase.push("undo");
}
if ( if (
!mapState || !mapState ||
mapState.mapDrawActionIndex === mapState.mapDrawActions.length - 1 mapState.mapDrawActionIndex === mapState.mapDrawActions.length - 1
) { ) {
disabledControls.push("redo"); disabledSettings.brush.push("redo");
disabledSettings.shape.push("redo");
disabledSettings.erase.push("redo");
}
if (fogShapes.length === 0) {
disabledSettings.fog.push("undo");
}
if (
!mapState ||
mapState.fogDrawActionIndex === mapState.fogDrawActions.length - 1
) {
disabledSettings.fog.push("redo");
} }
/** /**
@ -262,8 +286,7 @@ function Map({
onToolSettingChange={handleToolSettingChange} onToolSettingChange={handleToolSettingChange}
onToolAction={handleToolAction} onToolAction={handleToolAction}
disabledControls={disabledControls} disabledControls={disabledControls}
onUndo={onMapUndo} disabledSettings={disabledSettings}
onRedo={onMapRedo}
/> />
); );
return ( return (

View File

@ -16,8 +16,6 @@ import FogToolIcon from "../../icons/FogToolIcon";
import BrushToolIcon from "../../icons/BrushToolIcon"; import BrushToolIcon from "../../icons/BrushToolIcon";
import ShapeToolIcon from "../../icons/ShapeToolIcon"; import ShapeToolIcon from "../../icons/ShapeToolIcon";
import EraseToolIcon from "../../icons/EraseToolIcon"; import EraseToolIcon from "../../icons/EraseToolIcon";
import UndoIcon from "../../icons/UndoIcon";
import RedoIcon from "../../icons/RedoIcon";
import ExpandMoreIcon from "../../icons/ExpandMoreIcon"; import ExpandMoreIcon from "../../icons/ExpandMoreIcon";
function MapContols({ function MapContols({
@ -30,8 +28,7 @@ function MapContols({
onToolSettingChange, onToolSettingChange,
onToolAction, onToolAction,
disabledControls, disabledControls,
onUndo, disabledSettings,
onRedo,
}) { }) {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
@ -93,25 +90,6 @@ function MapContols({
</RadioIconButton> </RadioIconButton>
)), )),
}, },
{
id: "history",
component: (
<>
<IconButton
onClick={onUndo}
disabled={disabledControls.includes("undo")}
>
<UndoIcon />
</IconButton>
<IconButton
onClick={onRedo}
disabled={disabledControls.includes("redo")}
>
<RedoIcon />
</IconButton>
</>
),
},
]; ];
let controls = null; let controls = null;
@ -191,6 +169,7 @@ function MapContols({
onToolSettingChange(selectedToolId, change) onToolSettingChange(selectedToolId, change)
} }
onToolAction={onToolAction} onToolAction={onToolAction}
disabledActions={disabledSettings[selectedToolId]}
/> />
</Box> </Box>
); );
@ -199,13 +178,6 @@ function MapContols({
} }
} }
// Move back to pan tool if selected tool becomes disabled
useEffect(() => {
if (disabledControls.includes(selectedToolId)) {
onSelectedToolChange("pan");
}
}, [disabledControls]);
// Stop map drawing from happening when selecting controls // Stop map drawing from happening when selecting controls
// Not using react events as they seem to trigger after dom events // Not using react events as they seem to trigger after dom events
useEffect(() => { useEffect(() => {

View File

@ -8,9 +8,17 @@ import RadioIconButton from "./RadioIconButton";
import BrushStrokeIcon from "../../../icons/BrushStrokeIcon"; import BrushStrokeIcon from "../../../icons/BrushStrokeIcon";
import BrushFillIcon from "../../../icons/BrushFillIcon"; import BrushFillIcon from "../../../icons/BrushFillIcon";
import UndoButton from "./UndoButton";
import RedoButton from "./RedoButton";
import Divider from "./Divider"; import Divider from "./Divider";
function BrushToolSettings({ settings, onSettingChange }) { function BrushToolSettings({
settings,
onSettingChange,
onToolAction,
disabledActions,
}) {
return ( return (
<Flex sx={{ alignItems: "center" }}> <Flex sx={{ alignItems: "center" }}>
<ColorControl <ColorControl
@ -37,6 +45,15 @@ function BrushToolSettings({ settings, onSettingChange }) {
useBlending={settings.useBlending} useBlending={settings.useBlending}
onBlendingChange={(useBlending) => onSettingChange({ useBlending })} onBlendingChange={(useBlending) => onSettingChange({ useBlending })}
/> />
<Divider vertical />
<UndoButton
onClick={() => onToolAction("mapUndo")}
disabled={disabledActions.includes("undo")}
/>
<RedoButton
onClick={() => onToolAction("mapRedo")}
disabled={disabledActions.includes("redo")}
/>
</Flex> </Flex>
); );
} }

View File

@ -3,7 +3,12 @@ import { Flex, IconButton } from "theme-ui";
import EraseAllIcon from "../../../icons/EraseAllIcon"; import EraseAllIcon from "../../../icons/EraseAllIcon";
function EraseToolSettings({ onToolAction }) { import UndoButton from "./UndoButton";
import RedoButton from "./RedoButton";
import Divider from "./Divider";
function EraseToolSettings({ onToolAction, disabledActions }) {
return ( return (
<Flex sx={{ alignItems: "center" }}> <Flex sx={{ alignItems: "center" }}>
<IconButton <IconButton
@ -13,6 +18,15 @@ function EraseToolSettings({ onToolAction }) {
> >
<EraseAllIcon /> <EraseAllIcon />
</IconButton> </IconButton>
<Divider vertical />
<UndoButton
onClick={() => onToolAction("mapUndo")}
disabled={disabledActions.includes("undo")}
/>
<RedoButton
onClick={() => onToolAction("mapRedo")}
disabled={disabledActions.includes("redo")}
/>
</Flex> </Flex>
); );
} }

View File

@ -9,9 +9,17 @@ import FogAddIcon from "../../../icons/FogAddIcon";
import FogRemoveIcon from "../../../icons/FogRemoveIcon"; import FogRemoveIcon from "../../../icons/FogRemoveIcon";
import FogToggleIcon from "../../../icons/FogToggleIcon"; import FogToggleIcon from "../../../icons/FogToggleIcon";
import UndoButton from "./UndoButton";
import RedoButton from "./RedoButton";
import Divider from "./Divider"; import Divider from "./Divider";
function BrushToolSettings({ settings, onSettingChange }) { function BrushToolSettings({
settings,
onSettingChange,
onToolAction,
disabledActions,
}) {
return ( return (
<Flex sx={{ alignItems: "center" }}> <Flex sx={{ alignItems: "center" }}>
<RadioIconButton <RadioIconButton
@ -48,6 +56,15 @@ function BrushToolSettings({ settings, onSettingChange }) {
onSettingChange({ useGridSnapping }) onSettingChange({ useGridSnapping })
} }
/> />
<Divider vertical />
<UndoButton
onClick={() => onToolAction("fogUndo")}
disabled={disabledActions.includes("undo")}
/>
<RedoButton
onClick={() => onToolAction("fogRedo")}
disabled={disabledActions.includes("redo")}
/>
</Flex> </Flex>
); );
} }

View File

@ -0,0 +1,14 @@
import React from "react";
import { IconButton } from "theme-ui";
import RedoIcon from "../../../icons/RedoIcon";
function RedoButton({ onClick, disabled }) {
return (
<IconButton onClick={onClick} disabled={disabled}>
<RedoIcon />
</IconButton>
);
}
export default RedoButton;

View File

@ -9,9 +9,17 @@ import ShapeRectangleIcon from "../../../icons/ShapeRectangleIcon";
import ShapeCircleIcon from "../../../icons/ShapeCircleIcon"; import ShapeCircleIcon from "../../../icons/ShapeCircleIcon";
import ShapeTriangleIcon from "../../../icons/ShapeTriangleIcon"; import ShapeTriangleIcon from "../../../icons/ShapeTriangleIcon";
import UndoButton from "./UndoButton";
import RedoButton from "./RedoButton";
import Divider from "./Divider"; import Divider from "./Divider";
function ShapeToolSettings({ settings, onSettingChange }) { function ShapeToolSettings({
settings,
onSettingChange,
onToolAction,
disabledActions,
}) {
return ( return (
<Flex sx={{ alignItems: "center" }}> <Flex sx={{ alignItems: "center" }}>
<ColorControl <ColorControl
@ -45,6 +53,15 @@ function ShapeToolSettings({ settings, onSettingChange }) {
useBlending={settings.useBlending} useBlending={settings.useBlending}
onBlendingChange={(useBlending) => onSettingChange({ useBlending })} onBlendingChange={(useBlending) => onSettingChange({ useBlending })}
/> />
<Divider vertical />
<UndoButton
onClick={() => onToolAction("mapUndo")}
disabled={disabledActions.includes("undo")}
/>
<RedoButton
onClick={() => onToolAction("mapRedo")}
disabled={disabledActions.includes("redo")}
/>
</Flex> </Flex>
); );
} }

View File

@ -0,0 +1,14 @@
import React from "react";
import { IconButton } from "theme-ui";
import UndoIcon from "../../../icons/UndoIcon";
function UndoButton({ onClick, disabled }) {
return (
<IconButton onClick={onClick} disabled={disabled}>
<UndoIcon />
</IconButton>
);
}
export default UndoButton;

View File

@ -118,82 +118,92 @@ function Game() {
} }
} }
function addNewMapDrawActions(actions) { function addMapDrawActions(actions, indexKey, actionsKey) {
setMapState((prevMapState) => { setMapState((prevMapState) => {
const newActions = [ const newActions = [
...prevMapState.mapDrawActions.slice( ...prevMapState[actionsKey].slice(0, prevMapState[indexKey] + 1),
0,
prevMapState.mapDrawActionIndex + 1
),
...actions, ...actions,
]; ];
const newIndex = newActions.length - 1; const newIndex = newActions.length - 1;
return { return {
...prevMapState, ...prevMapState,
mapDrawActions: newActions, [actionsKey]: newActions,
mapDrawActionIndex: newIndex, [indexKey]: newIndex,
}; };
}); });
} }
function updateDrawActionIndex(change, indexKey, actionsKey, peerId) {
const newIndex = Math.min(
Math.max(mapState[indexKey] + change, -1),
mapState[actionsKey].length - 1
);
setMapState((prevMapState) => ({
...prevMapState,
[indexKey]: newIndex,
}));
return newIndex;
}
function handleMapDraw(action) { function handleMapDraw(action) {
addNewMapDrawActions([action]); addMapDrawActions([action], "mapDrawActionIndex", "mapDrawActions");
for (let peer of Object.values(peers)) { for (let peer of Object.values(peers)) {
peer.connection.send({ id: "mapDraw", data: [action] }); peer.connection.send({ id: "mapDraw", data: [action] });
} }
} }
function handleMapUndo() { function handleMapDrawUndo() {
// TODO: Check whether to pull from draw actions or fog actions const index = updateDrawActionIndex(
const newIndex = Math.max(mapState.mapDrawActionIndex - 1, -1); -1,
setMapState((prevMapState) => ({ "mapDrawActionIndex",
...prevMapState, "mapDrawActions"
mapDrawActionIndex: newIndex,
}));
for (let peer of Object.values(peers)) {
peer.connection.send({ id: "mapDrawIndex", data: newIndex });
}
}
function handleMapRedo() {
const newIndex = Math.min(
mapState.mapDrawActionIndex + 1,
mapState.mapDrawActions.length - 1
); );
setMapState((prevMapState) => ({
...prevMapState,
mapDrawActionIndex: newIndex,
}));
for (let peer of Object.values(peers)) { for (let peer of Object.values(peers)) {
peer.connection.send({ id: "mapDrawIndex", data: newIndex }); peer.connection.send({ id: "mapDrawIndex", data: index });
} }
} }
function addNewFogDrawActions(actions) { function handleMapDrawRedo() {
setMapState((prevMapState) => { const index = updateDrawActionIndex(
const newActions = [ 1,
...prevMapState.fogDrawActions.slice( "mapDrawActionIndex",
0, "mapDrawActions"
prevMapState.fogDrawActionIndex + 1 );
), for (let peer of Object.values(peers)) {
...actions, peer.connection.send({ id: "mapDrawIndex", data: index });
]; }
const newIndex = newActions.length - 1;
return {
...prevMapState,
fogDrawActions: newActions,
fogDrawActionIndex: newIndex,
};
});
} }
function handleFogDraw(action) { function handleFogDraw(action) {
addNewFogDrawActions([action]); addMapDrawActions([action], "fogDrawActionIndex", "fogDrawActions");
for (let peer of Object.values(peers)) { for (let peer of Object.values(peers)) {
peer.connection.send({ id: "mapFog", data: [action] }); peer.connection.send({ id: "mapFog", data: [action] });
} }
} }
function handleFogDrawUndo() {
const index = updateDrawActionIndex(
-1,
"fogDrawActionIndex",
"fogDrawActions"
);
for (let peer of Object.values(peers)) {
peer.connection.send({ id: "fogDrawIndex", data: index });
}
}
function handleFogDrawRedo() {
const index = updateDrawActionIndex(
1,
"fogDrawActionIndex",
"fogDrawActions"
);
for (let peer of Object.values(peers)) {
peer.connection.send({ id: "fogDrawIndex", data: index });
}
}
/** /**
* Party state * Party state
*/ */
@ -261,7 +271,7 @@ function Game() {
})); }));
} }
if (data.id === "mapDraw") { if (data.id === "mapDraw") {
addNewMapDrawActions(data.data); addMapDrawActions(data.data, "mapDrawActionIndex", "mapDrawActions");
} }
if (data.id === "mapDrawIndex") { if (data.id === "mapDrawIndex") {
setMapState((prevMapState) => ({ setMapState((prevMapState) => ({
@ -270,7 +280,7 @@ function Game() {
})); }));
} }
if (data.id === "mapFog") { if (data.id === "mapFog") {
addNewFogDrawActions(data.data); addMapDrawActions(data.data, "fogDrawActionIndex", "fogDrawActions");
} }
if (data.id === "mapFogIndex") { if (data.id === "mapFogIndex") {
setMapState((prevMapState) => ({ setMapState((prevMapState) => ({
@ -412,9 +422,11 @@ function Game() {
onMapChange={handleMapChange} onMapChange={handleMapChange}
onMapStateChange={handleMapStateChange} onMapStateChange={handleMapStateChange}
onMapDraw={handleMapDraw} onMapDraw={handleMapDraw}
onMapUndo={handleMapUndo} onMapDrawUndo={handleMapDrawUndo}
onMapRedo={handleMapRedo} onMapDrawRedo={handleMapDrawRedo}
onFogDraw={handleFogDraw} onFogDraw={handleFogDraw}
onFogDrawUndo={handleFogDrawUndo}
onFogDrawRedo={handleFogDrawRedo}
allowMapDrawing={canEditMapDrawing} allowMapDrawing={canEditMapDrawing}
allowFogDrawing={canEditFogDrawing} allowFogDrawing={canEditFogDrawing}
allowTokenChange={canEditTokens} allowTokenChange={canEditTokens}