Files
grungnet/src/components/Map.js

198 lines
5.0 KiB
JavaScript
Raw Normal View History

2020-03-20 21:46:52 +11:00
import React, { useRef, useEffect, useState } from "react";
import { Box, Image } from "theme-ui";
2020-03-20 21:46:52 +11:00
import interact from "interactjs";
import Token from "../components/Token";
2020-03-20 13:33:12 +11:00
import ProxyToken from "../components/ProxyToken";
import AddMapButton from "../components/AddMapButton";
2020-03-20 13:33:12 +11:00
const mapTokenClassName = "map-token";
2020-03-20 21:46:52 +11:00
const zoomSpeed = -0.005;
const minZoom = 0.1;
const maxZoom = 5;
2020-03-20 13:33:12 +11:00
function Map({
mapSource,
mapData,
tokens,
onMapTokenMove,
onMapTokenRemove,
2020-04-07 11:47:06 +10:00
onMapChanged,
}) {
2020-03-20 13:33:12 +11:00
function handleProxyDragEnd(isOnMap, token) {
if (isOnMap && onMapTokenMove) {
onMapTokenMove(token);
}
if (!isOnMap && onMapTokenRemove) {
onMapTokenRemove(token);
}
}
2020-03-20 21:46:52 +11:00
const [mapTranslate, setMapTranslate] = useState({ x: 0, y: 0 });
const [mapScale, setMapScale] = useState(1);
useEffect(() => {
interact(".map")
.gesturable({
listeners: {
2020-04-07 11:47:06 +10:00
move: (event) => {
setMapScale((previousMapScale) =>
Math.max(Math.min(previousMapScale + event.ds, maxZoom), minZoom)
);
2020-04-07 11:47:06 +10:00
setMapTranslate((previousMapTranslate) => ({
x: previousMapTranslate.x + event.dx,
2020-04-07 11:47:06 +10:00
y: previousMapTranslate.y + event.dy,
}));
2020-04-07 11:47:06 +10:00
},
},
})
.draggable({
inertia: true,
listeners: {
2020-04-07 11:47:06 +10:00
move: (event) => {
setMapTranslate((previousMapTranslate) => ({
x: previousMapTranslate.x + event.dx,
2020-04-07 11:47:06 +10:00
y: previousMapTranslate.y + event.dy,
}));
2020-04-07 11:47:06 +10:00
},
},
});
2020-04-07 11:47:06 +10:00
interact(".map").on("doubletap", (event) => {
2020-03-20 21:46:52 +11:00
event.preventDefault();
setMapTranslate({ x: 0, y: 0 });
setMapScale(1);
});
}, []);
function handleZoom(event) {
const deltaY = event.deltaY * zoomSpeed;
2020-04-07 11:47:06 +10:00
setMapScale((previousMapScale) =>
Math.max(Math.min(previousMapScale + deltaY, maxZoom), minZoom)
2020-03-20 21:46:52 +11:00
);
}
// Reset map transform when map changes
useEffect(() => {
setMapTranslate({ x: 0, y: 0 });
setMapScale(1);
}, [mapSource]);
const mapRef = useRef(null);
const rows = mapData && mapData.rows;
const tokenSizePercent = (1 / rows) * 100;
const aspectRatio = (mapData && mapData.width / mapData.height) || 1;
return (
2020-03-20 13:33:12 +11:00
<>
<Box
2020-03-20 13:33:12 +11:00
className="map"
sx={{
flexGrow: 1,
position: "relative",
overflow: "hidden",
backgroundColor: "rgba(0, 0, 0, 0.1)",
userSelect: "none",
2020-04-07 11:47:06 +10:00
touchAction: "none",
}}
2020-03-20 13:33:12 +11:00
bg="background"
2020-03-20 21:46:52 +11:00
onWheel={handleZoom}
>
2020-03-20 13:33:12 +11:00
<Box
sx={{
position: "relative",
top: "50%",
left: "50%",
2020-04-07 11:47:06 +10:00
transform: "translate(-50%, -50%)",
2020-03-20 13:33:12 +11:00
}}
>
<Box
2020-03-20 21:46:52 +11:00
style={{
2020-04-07 11:47:06 +10:00
transform: `translate(${mapTranslate.x}px, ${mapTranslate.y}px) scale(${mapScale})`,
}}
>
2020-03-20 21:46:52 +11:00
<Box
sx={{
width: "100%",
height: 0,
2020-04-07 11:47:06 +10:00
paddingBottom: `${(1 / aspectRatio) * 100}%`,
2020-03-20 21:46:52 +11:00
}}
/>
<Box
sx={{
position: "absolute",
top: 0,
right: 0,
bottom: 0,
2020-04-07 11:47:06 +10:00
left: 0,
2020-03-20 21:46:52 +11:00
}}
>
<Image
ref={mapRef}
id="map"
sx={{
width: "100%",
userSelect: "none",
2020-04-07 11:47:06 +10:00
touchAction: "none",
}}
2020-03-20 21:46:52 +11:00
src={mapSource}
/>
</Box>
<Box
sx={{
position: "absolute",
top: 0,
left: 0,
right: 0,
2020-04-07 11:47:06 +10:00
bottom: 0,
2020-03-20 21:46:52 +11:00
}}
>
2020-04-07 11:47:06 +10:00
{Object.values(tokens).map((token) => (
2020-03-20 21:46:52 +11:00
<Box
key={token.id}
style={{
left: `${token.x * 100}%`,
top: `${token.y * 100}%`,
2020-04-07 11:47:06 +10:00
width: `${tokenSizePercent * (token.size || 1)}%`,
2020-03-20 21:46:52 +11:00
}}
sx={{
position: "absolute",
2020-04-07 11:47:06 +10:00
display: "flex",
2020-03-20 21:46:52 +11:00
}}
>
<Token
2020-04-07 11:47:06 +10:00
data={{
id: token.id,
size: token.size,
}}
2020-03-20 21:46:52 +11:00
image={token.image}
className={mapTokenClassName}
sx={{ position: "absolute" }}
2020-03-20 21:46:52 +11:00
/>
</Box>
))}
</Box>
</Box>
2020-03-20 13:33:12 +11:00
</Box>
<Box
p={2}
sx={{
position: "absolute",
top: "0",
left: "50%",
2020-04-07 11:47:06 +10:00
transform: "translateX(-50%)",
}}
>
<AddMapButton onMapChanged={onMapChanged} />
</Box>
</Box>
2020-03-20 13:33:12 +11:00
<ProxyToken
tokenClassName={mapTokenClassName}
onProxyDragEnd={handleProxyDragEnd}
/>
</>
);
}
export default Map;