Files
grungnet/src/components/map/Map.js

319 lines
8.3 KiB
JavaScript
Raw Normal View History

2020-05-22 13:47:11 +10:00
import React, { useState, useContext, useEffect } from "react";
import MapControls from "./MapControls";
import MapInteraction from "./MapInteraction";
2020-04-18 18:11:21 +10:00
import MapToken from "./MapToken";
import MapDrawing from "./MapDrawing";
import MapFog from "./MapFog";
import MapDice from "./MapDice";
2020-03-20 13:33:12 +11:00
import TokenDataContext from "../../contexts/TokenDataContext";
import MapLoadingContext from "../../contexts/MapLoadingContext";
2020-05-21 16:46:50 +10:00
import TokenMenu from "../token/TokenMenu";
2020-05-21 22:57:44 +10:00
import TokenDragOverlay from "../token/TokenDragOverlay";
import LoadingOverlay from "../LoadingOverlay";
2020-05-22 13:47:11 +10:00
import { omit } from "../../helpers/shared";
2020-03-20 13:33:12 +11:00
function Map({
map,
2020-04-23 17:23:34 +10:00
mapState,
onMapTokenStateChange,
onMapTokenStateRemove,
onMapChange,
onMapStateChange,
2020-04-19 15:15:48 +10:00
onMapDraw,
onMapDrawUndo,
onMapDrawRedo,
onFogDraw,
onFogDrawUndo,
onFogDrawRedo,
2020-04-29 21:12:57 +10:00
allowMapDrawing,
allowFogDrawing,
disabledTokens,
}) {
const { tokensById } = useContext(TokenDataContext);
const { isLoading } = useContext(MapLoadingContext);
2020-05-21 16:46:50 +10:00
const gridX = map && map.gridX;
const gridY = map && map.gridY;
const gridSizeNormalized = {
x: gridX ? 1 / gridX : 0,
y: gridY ? 1 / gridY : 0,
};
2020-05-21 16:46:50 +10:00
const tokenSizePercent = gridSizeNormalized.x;
2020-03-20 21:46:52 +11:00
const [selectedToolId, setSelectedToolId] = useState("pan");
const [toolSettings, setToolSettings] = useState({
2020-04-28 17:03:17 +10:00
fog: { type: "add", useEdgeSnapping: true, useGridSnapping: false },
brush: {
color: "darkGray",
type: "stroke",
useBlending: false,
},
shape: {
color: "red",
type: "rectangle",
useBlending: true,
},
});
2020-04-29 20:55:52 +10:00
function handleToolSettingChange(tool, change) {
setToolSettings((prevSettings) => ({
...prevSettings,
[tool]: {
...prevSettings[tool],
...change,
},
}));
}
2020-04-19 00:24:06 +10:00
2020-04-29 20:55:52 +10:00
function handleToolAction(action) {
if (action === "eraseAll") {
onMapDraw({
type: "remove",
shapeIds: mapShapes.map((s) => s.id),
timestamp: Date.now(),
});
}
if (action === "mapUndo") {
onMapDrawUndo();
}
if (action === "mapRedo") {
onMapDrawRedo();
}
if (action === "fogUndo") {
onFogDrawUndo();
}
if (action === "fogRedo") {
onFogDrawRedo();
}
2020-04-29 20:55:52 +10:00
}
const [mapShapes, setMapShapes] = useState([]);
2020-05-22 17:22:32 +10:00
function handleMapShapeAdd(shape) {
onMapDraw({ type: "add", shapes: [shape] });
}
function handleMapShapesRemove(shapeIds) {
onMapDraw({ type: "remove", shapeIds });
}
const [fogShapes, setFogShapes] = useState([]);
function handleFogShapeAdd(shape) {
onFogDraw({ type: "add", shapes: [shape] });
}
function handleFogShapesRemove(shapeIds) {
onFogDraw({ type: "remove", shapeIds });
}
function handleFogShapesEdit(shapes) {
onFogDraw({ type: "edit", shapes });
2020-04-29 20:40:34 +10:00
}
2020-04-19 15:15:48 +10:00
// Replay the draw actions and convert them to shapes for the map drawing
useEffect(() => {
2020-04-23 17:23:34 +10:00
if (!mapState) {
return;
}
function actionsToShapes(actions, actionIndex) {
let shapesById = {};
for (let i = 0; i <= actionIndex; i++) {
const action = actions[i];
2020-04-29 20:40:34 +10:00
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)
);
2020-04-23 17:23:34 +10:00
}, [mapState]);
const disabledControls = [];
2020-04-29 21:12:57 +10:00
if (!allowMapDrawing) {
disabledControls.push("brush");
disabledControls.push("shape");
disabledControls.push("erase");
}
if (!map) {
disabledControls.push("pan");
}
if (mapShapes.length === 0) {
disabledControls.push("erase");
}
2020-04-29 21:12:57 +10:00
if (!allowFogDrawing) {
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 (
!mapState ||
mapState.mapDrawActionIndex === mapState.mapDrawActions.length - 1
) {
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");
}
const mapControls = (
<MapControls
onMapChange={onMapChange}
onMapStateChange={onMapStateChange}
currentMap={map}
currentMapState={mapState}
onSelectedToolChange={setSelectedToolId}
selectedToolId={selectedToolId}
toolSettings={toolSettings}
onToolSettingChange={handleToolSettingChange}
2020-04-29 20:55:52 +10:00
onToolAction={handleToolAction}
disabledControls={disabledControls}
disabledSettings={disabledSettings}
/>
);
2020-05-21 16:46:50 +10:00
const [isTokenMenuOpen, setIsTokenMenuOpen] = useState(false);
const [tokenMenuOptions, setTokenMenuOptions] = useState({});
const [draggingTokenOptions, setDraggingTokenOptions] = useState();
2020-05-21 16:46:50 +10:00
function handleTokenMenuOpen(tokenStateId, tokenImage) {
setTokenMenuOptions({ tokenStateId, tokenImage });
setIsTokenMenuOpen(true);
}
// Sort so vehicles render below other tokens
function sortMapTokenStates(a, b) {
const tokenA = tokensById[a.tokenId];
const tokenB = tokensById[b.tokenId];
if (tokenA && tokenB) {
return tokenB.isVehicle - tokenA.isVehicle;
} else if (tokenA) {
return 1;
} else if (tokenB) {
return -1;
} else {
return 0;
}
}
2020-05-21 16:46:50 +10:00
const mapTokens =
mapState &&
Object.values(mapState.tokens)
.sort(sortMapTokenStates)
.map((tokenState) => (
<MapToken
key={tokenState.id}
token={tokensById[tokenState.tokenId]}
tokenState={tokenState}
tokenSizePercent={tokenSizePercent}
onTokenStateChange={onMapTokenStateChange}
onTokenMenuOpen={handleTokenMenuOpen}
onTokenDragStart={(e) =>
setDraggingTokenOptions({ tokenState, tokenImage: e.target })
}
onTokenDragEnd={() => setDraggingTokenOptions(null)}
draggable={
(selectedToolId === "pan" || selectedToolId === "erase") &&
!(tokenState.id in disabledTokens)
}
mapState={mapState}
/>
));
2020-05-22 13:46:52 +10:00
const tokenMenu = (
2020-05-21 16:46:50 +10:00
<TokenMenu
isOpen={isTokenMenuOpen}
onRequestClose={() => setIsTokenMenuOpen(false)}
onTokenStateChange={onMapTokenStateChange}
2020-05-22 13:46:52 +10:00
tokenState={mapState && mapState.tokens[tokenMenuOptions.tokenStateId]}
2020-05-21 16:46:50 +10:00
tokenImage={tokenMenuOptions.tokenImage}
/>
);
const tokenDragOverlay = draggingTokenOptions && (
2020-05-21 22:57:44 +10:00
<TokenDragOverlay
onTokenStateRemove={(state) => {
onMapTokenStateRemove(state);
setDraggingTokenOptions(null);
}}
onTokenStateChange={onMapTokenStateChange}
tokenState={draggingTokenOptions && draggingTokenOptions.tokenState}
tokenImage={draggingTokenOptions && draggingTokenOptions.tokenImage}
token={tokensById[draggingTokenOptions.tokenState.tokenId]}
mapState={mapState}
2020-05-21 22:57:44 +10:00
/>
);
const mapDrawing = (
<MapDrawing
shapes={mapShapes}
onShapeAdd={handleMapShapeAdd}
onShapesRemove={handleMapShapesRemove}
2020-05-22 13:47:11 +10:00
selectedToolId={selectedToolId}
selectedToolSettings={toolSettings[selectedToolId]}
gridSize={gridSizeNormalized}
/>
);
const mapFog = (
<MapFog
shapes={fogShapes}
onShapeAdd={handleFogShapeAdd}
onShapesRemove={handleFogShapesRemove}
onShapesEdit={handleFogShapesEdit}
2020-05-22 17:22:32 +10:00
selectedToolId={selectedToolId}
selectedToolSettings={toolSettings[selectedToolId]}
gridSize={gridSizeNormalized}
/>
);
return (
2020-05-21 16:46:50 +10:00
<MapInteraction
map={map}
controls={
<>
{mapControls}
{tokenMenu}
2020-05-21 22:57:44 +10:00
{tokenDragOverlay}
<MapDice />
{isLoading && <LoadingOverlay />}
2020-05-21 16:46:50 +10:00
</>
}
2020-05-22 13:47:11 +10:00
selectedToolId={selectedToolId}
2020-05-21 16:46:50 +10:00
>
2020-05-22 13:47:11 +10:00
{mapDrawing}
2020-05-21 16:46:50 +10:00
{mapTokens}
2020-05-22 17:22:32 +10:00
{mapFog}
2020-05-21 16:46:50 +10:00
</MapInteraction>
);
}
export default Map;