2020-05-21 16:46:50 +10:00
|
|
|
import React, { useEffect, useRef, useState, useContext } from "react";
|
2020-03-20 11:05:40 +11:00
|
|
|
import ReactDOM from "react-dom";
|
|
|
|
|
import { Image, Box } from "theme-ui";
|
|
|
|
|
import interact from "interactjs";
|
|
|
|
|
|
2020-04-23 10:09:12 +10:00
|
|
|
import usePortal from "../../helpers/usePortal";
|
2020-04-14 09:41:10 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
import MapStageContext from "../../contexts/MapStageContext";
|
|
|
|
|
|
2020-04-24 15:50:05 +10:00
|
|
|
/**
|
|
|
|
|
* @callback onProxyDragEnd
|
|
|
|
|
* @param {boolean} isOnMap whether the token was dropped on the map
|
|
|
|
|
* @param {Object} token the token that was dropped
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {string} tokenClassName The class name to attach the interactjs handler to
|
|
|
|
|
* @param {onProxyDragEnd} onProxyDragEnd Called when the proxy token is dropped
|
|
|
|
|
* @param {Object} tokens An optional mapping of tokens to use as a base when calling OnProxyDragEnd
|
2020-04-30 15:12:34 +10:00
|
|
|
|
2020-04-24 15:50:05 +10:00
|
|
|
*/
|
2020-05-21 20:57:52 +10:00
|
|
|
function ProxyToken({ tokenClassName, onProxyDragEnd, tokens }) {
|
2020-03-20 11:05:40 +11:00
|
|
|
const proxyContainer = usePortal("root");
|
|
|
|
|
|
|
|
|
|
const [imageSource, setImageSource] = useState("");
|
2020-04-13 11:22:10 +10:00
|
|
|
const proxyRef = useRef();
|
2020-03-20 11:05:40 +11:00
|
|
|
|
2020-04-24 15:50:05 +10:00
|
|
|
// Store the tokens in a ref and access in the interactjs loop
|
|
|
|
|
// This is needed to stop interactjs from creating multiple listeners
|
|
|
|
|
const tokensRef = useRef(tokens);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
tokensRef.current = tokens;
|
2020-05-21 20:57:52 +10:00
|
|
|
}, [tokens]);
|
2020-04-24 15:50:05 +10:00
|
|
|
|
2020-03-20 11:05:40 +11:00
|
|
|
const proxyOnMap = useRef(false);
|
2020-05-21 16:46:50 +10:00
|
|
|
const mapStageRef = useContext(MapStageContext);
|
2020-03-20 11:05:40 +11:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2020-04-21 18:01:45 +10:00
|
|
|
interact(`.${tokenClassName}`).draggable({
|
2020-03-20 11:05:40 +11:00
|
|
|
listeners: {
|
2020-04-07 11:47:06 +10:00
|
|
|
start: (event) => {
|
2020-03-20 11:05:40 +11:00
|
|
|
let target = event.target;
|
2020-04-30 15:12:34 +10:00
|
|
|
|
2020-03-20 11:05:40 +11:00
|
|
|
// Hide the token and copy it's image to the proxy
|
2020-04-13 11:22:10 +10:00
|
|
|
target.parentElement.style.opacity = "0.25";
|
2020-03-20 11:05:40 +11:00
|
|
|
setImageSource(target.src);
|
|
|
|
|
|
2020-04-13 11:22:10 +10:00
|
|
|
let proxy = proxyRef.current;
|
2020-03-20 11:05:40 +11:00
|
|
|
if (proxy) {
|
|
|
|
|
// Find and set the initial offset of the token to the proxy
|
|
|
|
|
const proxyRect = proxy.getBoundingClientRect();
|
|
|
|
|
const targetRect = target.getBoundingClientRect();
|
|
|
|
|
const xOffset = targetRect.left - proxyRect.left;
|
|
|
|
|
const yOffset = targetRect.top - proxyRect.top;
|
|
|
|
|
proxy.style.transform = `translate(${xOffset}px, ${yOffset}px)`;
|
|
|
|
|
proxy.setAttribute("data-x", xOffset);
|
|
|
|
|
proxy.setAttribute("data-y", yOffset);
|
2020-03-26 15:07:27 +11:00
|
|
|
|
|
|
|
|
// Copy width and height of target
|
|
|
|
|
proxy.style.width = `${targetRect.width}px`;
|
|
|
|
|
proxy.style.height = `${targetRect.height}px`;
|
2020-03-20 11:05:40 +11:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2020-04-07 11:47:06 +10:00
|
|
|
move: (event) => {
|
2020-04-13 11:22:10 +10:00
|
|
|
let proxy = proxyRef.current;
|
2020-03-20 11:05:40 +11:00
|
|
|
// Move the proxy based off of the movment of the token
|
|
|
|
|
if (proxy) {
|
|
|
|
|
// keep the dragged position in the data-x/data-y attributes
|
|
|
|
|
const x =
|
|
|
|
|
(parseFloat(proxy.getAttribute("data-x")) || 0) + event.dx;
|
|
|
|
|
const y =
|
|
|
|
|
(parseFloat(proxy.getAttribute("data-y")) || 0) + event.dy;
|
|
|
|
|
proxy.style.transform = `translate(${x}px, ${y}px)`;
|
|
|
|
|
|
|
|
|
|
// Check whether the proxy is on the right or left hand side of the screen
|
|
|
|
|
// if not set proxyOnMap to true
|
2020-04-14 09:41:10 +10:00
|
|
|
const proxyRect = proxy.getBoundingClientRect();
|
|
|
|
|
const map = document.querySelector(".map");
|
|
|
|
|
const mapRect = map.getBoundingClientRect();
|
|
|
|
|
proxyOnMap.current =
|
|
|
|
|
proxyRect.left > mapRect.left && proxyRect.right < mapRect.right;
|
2020-03-20 11:05:40 +11:00
|
|
|
|
|
|
|
|
// update the posiion attributes
|
|
|
|
|
proxy.setAttribute("data-x", x);
|
|
|
|
|
proxy.setAttribute("data-y", y);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2020-04-07 11:47:06 +10:00
|
|
|
end: (event) => {
|
2020-03-20 11:05:40 +11:00
|
|
|
let target = event.target;
|
2020-04-30 15:12:34 +10:00
|
|
|
const id = target.dataset.id;
|
2020-04-13 11:22:10 +10:00
|
|
|
let proxy = proxyRef.current;
|
2020-03-20 11:05:40 +11:00
|
|
|
if (proxy) {
|
2020-05-21 16:46:50 +10:00
|
|
|
const mapStage = mapStageRef.current;
|
|
|
|
|
if (onProxyDragEnd && mapStage) {
|
|
|
|
|
const mapImageRect = mapStage
|
|
|
|
|
.findOne("#mapImage")
|
|
|
|
|
.getClientRect();
|
|
|
|
|
|
|
|
|
|
const map = document.querySelector(".map");
|
|
|
|
|
const mapRect = map.getBoundingClientRect();
|
2020-03-20 17:56:34 +11:00
|
|
|
|
|
|
|
|
let x = parseFloat(proxy.getAttribute("data-x")) || 0;
|
|
|
|
|
let y = parseFloat(proxy.getAttribute("data-y")) || 0;
|
2020-05-21 16:46:50 +10:00
|
|
|
|
|
|
|
|
// TODO: This seems to be wrong when map is zoomed
|
|
|
|
|
|
2020-03-20 17:56:34 +11:00
|
|
|
// Convert coordiantes to be relative to the map
|
2020-05-21 16:46:50 +10:00
|
|
|
x = x - mapRect.left - mapImageRect.x;
|
|
|
|
|
y = y - mapRect.top - mapImageRect.y;
|
|
|
|
|
|
2020-03-20 17:56:34 +11:00
|
|
|
// Normalize to map width
|
2020-05-21 16:46:50 +10:00
|
|
|
x = x / mapImageRect.width;
|
|
|
|
|
y = y / mapImageRect.height;
|
2020-03-20 17:56:34 +11:00
|
|
|
|
2020-04-24 15:50:05 +10:00
|
|
|
// Get the token from the supplied tokens if it exists
|
|
|
|
|
const token = tokensRef.current[id] || {};
|
2020-04-13 00:24:03 +10:00
|
|
|
|
2020-03-20 11:05:40 +11:00
|
|
|
onProxyDragEnd(proxyOnMap.current, {
|
2020-04-24 15:50:05 +10:00
|
|
|
...token,
|
|
|
|
|
x,
|
|
|
|
|
y,
|
2020-03-20 11:05:40 +11:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset the proxy position
|
|
|
|
|
proxy.style.transform = "translate(0px, 0px)";
|
|
|
|
|
proxy.setAttribute("data-x", 0);
|
|
|
|
|
proxy.setAttribute("data-y", 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Show the token
|
2020-04-13 11:22:10 +10:00
|
|
|
target.parentElement.style.opacity = "1";
|
2020-03-20 11:05:40 +11:00
|
|
|
setImageSource("");
|
2020-04-07 11:47:06 +10:00
|
|
|
},
|
|
|
|
|
},
|
2020-03-20 11:05:40 +11:00
|
|
|
});
|
2020-05-21 16:46:50 +10:00
|
|
|
}, [onProxyDragEnd, tokenClassName, proxyContainer, mapStageRef]);
|
2020-03-20 11:05:40 +11:00
|
|
|
|
|
|
|
|
if (!imageSource) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a portal to allow the proxy to move past the bounds of the token
|
|
|
|
|
return ReactDOM.createPortal(
|
|
|
|
|
<Box
|
|
|
|
|
sx={{
|
|
|
|
|
position: "absolute",
|
|
|
|
|
overflow: "hidden",
|
|
|
|
|
top: 0,
|
2020-03-26 14:53:02 +11:00
|
|
|
left: 0,
|
|
|
|
|
bottom: 0,
|
2020-04-07 11:47:06 +10:00
|
|
|
right: 0,
|
2020-03-20 11:05:40 +11:00
|
|
|
}}
|
|
|
|
|
>
|
2020-04-13 11:22:10 +10:00
|
|
|
<Box
|
|
|
|
|
sx={{ position: "absolute", display: "flex", flexDirection: "column" }}
|
|
|
|
|
ref={proxyRef}
|
|
|
|
|
>
|
|
|
|
|
<Image
|
|
|
|
|
src={imageSource}
|
|
|
|
|
sx={{
|
|
|
|
|
touchAction: "none",
|
|
|
|
|
userSelect: "none",
|
|
|
|
|
width: "100%",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
2020-03-20 11:05:40 +11:00
|
|
|
</Box>,
|
|
|
|
|
proxyContainer
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 15:50:05 +10:00
|
|
|
ProxyToken.defaultProps = {
|
|
|
|
|
tokens: {},
|
|
|
|
|
};
|
|
|
|
|
|
2020-03-20 11:05:40 +11:00
|
|
|
export default ProxyToken;
|