import React, { useEffect, useState } from "react"; import { Box, Input, Slider, Flex, Text } from "theme-ui"; import MapMenu from "../map/MapMenu"; import colors, { colorOptions } from "../../helpers/colors"; import usePrevious from "../../helpers/usePrevious"; const defaultTokenMaxSize = 6; /** * @callback onTokenChange * @param {Object} token the token that was changed */ /** * * @param {string} tokenClassName The class name to attach the interactjs handler to * @param {onProxyDragEnd} onTokenChange Called when the the token data is changed * @param {Object} tokens An mapping of tokens to use as a base when calling onTokenChange * @param {Object} disabledTokens An optional mapping of tokens that shouldn't allow interaction */ function TokenMenu({ isOpen, onRequestClose, tokenState, tokenImage, onTokenChange, }) { const wasOpen = usePrevious(isOpen); const [tokenMaxSize, setTokenMaxSize] = useState(defaultTokenMaxSize); useEffect(() => { if (isOpen && !wasOpen && tokenState) { setTokenMaxSize(Math.max(tokenState.size, defaultTokenMaxSize)); } }, [isOpen, tokenState, wasOpen]); function handleLabelChange(event) { const label = event.target.value; onTokenChange({ ...tokenState, label: label }); } const [menuLeft, setMenuLeft] = useState(0); const [menuTop, setMenuTop] = useState(0); useEffect(() => { if (tokenImage) { const imageRect = tokenImage.getClientRect(); const map = document.querySelector(".map"); const mapRect = map.getBoundingClientRect(); // Center X for the menu which is 156px wide setMenuLeft(mapRect.left + imageRect.x + imageRect.width / 2 - 156 / 2); // Y 12px from the bottom setMenuTop(mapRect.top + imageRect.y + imageRect.height + 12); } }, [tokenImage]); function handleStatusChange(status) { const statuses = tokenState.statuses; let newStatuses = []; if (statuses.includes(status)) { newStatuses = statuses.filter((s) => s !== status); } else { newStatuses = [...statuses, status]; } onTokenChange({ ...tokenState, statuses: newStatuses }); } function handleSizeChange(event) { const newSize = parseInt(event.target.value); onTokenChange({ ...tokenState, size: newSize }); } function handleRotationChange(event) { const newRotation = parseInt(event.target.value); onTokenChange({ ...tokenState, rotation: newRotation }); } function handleModalContent(node) { if (node) { // Focus input const tokenLabelInput = node.querySelector("#changeTokenLabel"); tokenLabelInput.focus(); tokenLabelInput.select(); // Ensure menu is in bounds const nodeRect = node.getBoundingClientRect(); const map = document.querySelector(".map"); const mapRect = map.getBoundingClientRect(); setMenuLeft((prevLeft) => Math.min( mapRect.right - nodeRect.width, Math.max(mapRect.left, prevLeft) ) ); setMenuTop((prevTop) => Math.min(mapRect.bottom - nodeRect.height, prevTop) ); } } return ( { e.preventDefault(); onRequestClose(); }} sx={{ alignItems: "center" }} > Label: {colorOptions.map((color) => ( handleStatusChange(color)} aria-label={`Token label Color ${color}`} > {tokenState && tokenState.statuses && tokenState.statuses.includes(color) && ( )} ))} Size: Rotation: ); } export default TokenMenu;