import React, { useRef, useEffect, useState } from "react";
import { Box, Image } from "theme-ui";
import ProxyToken from "../token/ProxyToken";
import TokenMenu from "../token/TokenMenu";
import MapToken from "./MapToken";
import MapDrawing from "./MapDrawing";
import MapFog from "./MapFog";
import MapControls from "./MapControls";
import { omit } from "../../helpers/shared";
import useDataSource from "../../helpers/useDataSource";
import MapInteraction from "./MapInteraction";
import { mapSources as defaultMapSources } from "../../maps";
const mapTokenProxyClassName = "map-token__proxy";
const mapTokenMenuClassName = "map-token__menu";
function Map({
map,
mapState,
tokens,
onMapTokenStateChange,
onMapTokenStateRemove,
onMapChange,
onMapStateChange,
onMapDraw,
onFogDraw,
onMapUndo,
onMapRedo,
allowDrawing,
allowTokenChange,
allowMapChange,
}) {
const mapSource = useDataSource(map, defaultMapSources);
function handleProxyDragEnd(isOnMap, tokenState) {
if (isOnMap && onMapTokenStateChange) {
onMapTokenStateChange(tokenState);
}
if (!isOnMap && onMapTokenStateRemove) {
onMapTokenStateRemove(tokenState);
}
}
/**
* Map drawing
*/
const [selectedToolId, setSelectedToolId] = useState("pan");
const [toolSettings, setToolSettings] = useState({
fog: { type: "add", useEdgeSnapping: true, useGridSnapping: false },
brush: {
color: "darkGray",
type: "stroke",
useBlending: false,
},
shape: {
color: "red",
type: "rectangle",
useBlending: true,
},
});
function handleToolSettingChange(tool, change) {
setToolSettings((prevSettings) => ({
...prevSettings,
[tool]: {
...prevSettings[tool],
...change,
},
}));
}
function handleToolAction(action) {
if (action === "eraseAll") {
onMapDraw({
type: "remove",
shapeIds: mapShapes.map((s) => s.id),
timestamp: Date.now(),
});
}
}
const [mapShapes, setMapShapes] = useState([]);
function handleMapShapeAdd(shape) {
onMapDraw({ type: "add", shapes: [shape], timestamp: Date.now() });
}
function handleMapShapeRemove(shapeId) {
onMapDraw({ type: "remove", shapeIds: [shapeId], timestamp: Date.now() });
}
const [fogShapes, setFogShapes] = useState([]);
function handleFogShapeAdd(shape) {
onFogDraw({ type: "add", shapes: [shape], timestamp: Date.now() });
}
function handleFogShapeRemove(shapeId) {
onFogDraw({ type: "remove", shapeIds: [shapeId], timestamp: Date.now() });
}
function handleFogShapeEdit(shape) {
onFogDraw({ type: "edit", shapes: [shape], timestamp: Date.now() });
}
// 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]);
const disabledControls = [];
if (!allowMapChange) {
disabledControls.push("map");
}
if (!allowDrawing) {
disabledControls.push("drawing");
}
if (!map) {
disabledControls.push("pan");
disabledControls.push("brush");
}
if (mapShapes.length === 0) {
disabledControls.push("erase");
}
if (!mapState || mapState.mapDrawActionIndex < 0) {
disabledControls.push("undo");
}
if (
!mapState ||
mapState.mapDrawActionIndex === mapState.mapDrawActions.length - 1
) {
disabledControls.push("redo");
}
/**
* Member setup
*/
const mapRef = useRef(null);
const gridX = map && map.gridX;
const gridY = map && map.gridY;
const gridSizeNormalized = { x: 1 / gridX || 0, y: 1 / gridY || 0 };
const tokenSizePercent = gridSizeNormalized.x * 100;
const aspectRatio = (map && map.width / map.height) || 1;
const mapImage = (
);
const mapTokens = (
{mapState &&
Object.values(mapState.tokens).map((tokenState) => (
token.id === tokenState.tokenId)}
tokenState={tokenState}
tokenSizePercent={tokenSizePercent}
className={`${mapTokenProxyClassName} ${mapTokenMenuClassName}`}
/>
))}
);
const mapDrawing = (
);
const mapFog = (
);
const mapControls = (
);
return (
<>
{map && mapImage}
{map && mapDrawing}
{map && mapFog}
{map && mapTokens}
{allowTokenChange && (
<>
>
)}
>
);
}
export default Map;