Added token status rings

This commit is contained in:
Mitchell McCaffrey 2020-04-13 23:42:18 +10:00
parent 8ba3881529
commit 441e589ec3
6 changed files with 172 additions and 24 deletions

View File

@ -2,6 +2,7 @@ import React, { useRef } from "react";
import { Box, Image } from "theme-ui"; import { Box, Image } from "theme-ui";
import TokenLabel from "./TokenLabel"; import TokenLabel from "./TokenLabel";
import TokenStatus from "./TokenStatus";
import usePreventTouch from "../helpers/usePreventTouch"; import usePreventTouch from "../helpers/usePreventTouch";
@ -51,8 +52,10 @@ function MapToken({ token, tokenSizePercent, className }) {
data-id={token.id} data-id={token.id}
data-size={token.size} data-size={token.size}
data-label={token.label} data-label={token.label}
data-status={token.status}
ref={imageRef} ref={imageRef}
/> />
{token.status && <TokenStatus statuses={token.status.split(" ")} />}
{token.label && <TokenLabel label={token.label} />} {token.label && <TokenLabel label={token.label} />}
</Box> </Box>
</Box> </Box>

View File

@ -5,12 +5,14 @@ import interact from "interactjs";
import usePortal from "../helpers/usePortal"; import usePortal from "../helpers/usePortal";
import TokenLabel from "./TokenLabel"; import TokenLabel from "./TokenLabel";
import TokenStatus from "./TokenStatus";
function ProxyToken({ tokenClassName, onProxyDragEnd }) { function ProxyToken({ tokenClassName, onProxyDragEnd }) {
const proxyContainer = usePortal("root"); const proxyContainer = usePortal("root");
const [imageSource, setImageSource] = useState(""); const [imageSource, setImageSource] = useState("");
const [label, setLabel] = useState(""); const [label, setLabel] = useState("");
const [status, setStatus] = useState("");
const proxyRef = useRef(); const proxyRef = useRef();
const proxyOnMap = useRef(false); const proxyOnMap = useRef(false);
@ -24,6 +26,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
target.parentElement.style.opacity = "0.25"; target.parentElement.style.opacity = "0.25";
setImageSource(target.src); setImageSource(target.src);
setLabel(target.dataset.label || ""); setLabel(target.dataset.label || "");
setStatus(target.dataset.status || "");
let proxy = proxyRef.current; let proxy = proxyRef.current;
if (proxy) { if (proxy) {
@ -141,6 +144,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
width: "100%", width: "100%",
}} }}
/> />
{status && <TokenStatus statuses={status.split(" ")} />}
{label && <TokenLabel label={label} />} {label && <TokenLabel label={label} />}
</Box> </Box>
</Box>, </Box>,

View File

@ -1,7 +1,9 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Modal from "react-modal"; import Modal from "react-modal";
import interact from "interactjs"; import interact from "interactjs";
import { useThemeUI, Box, Input } from "theme-ui"; import { Box, Input } from "theme-ui";
import { statusOptions, statusToColor } from "../helpers/status";
function TokenMenu({ tokenClassName, onTokenChange }) { function TokenMenu({ tokenClassName, onTokenChange }) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -10,7 +12,7 @@ function TokenMenu({ tokenClassName, onTokenChange }) {
setIsOpen(false); setIsOpen(false);
} }
const [currentToken, setCurrentToken] = useState(0); const [currentToken, setCurrentToken] = useState({});
const [menuLeft, setMenuLeft] = useState(0); const [menuLeft, setMenuLeft] = useState(0);
const [menuTop, setMenuTop] = useState(0); const [menuTop, setMenuTop] = useState(0);
@ -27,6 +29,23 @@ function TokenMenu({ tokenClassName, onTokenChange }) {
} }
} }
function handleStatusChange(status) {
const statuses =
currentToken.status.split(" ").filter((s) => s !== "") || [];
let newStatuses = [];
if (statuses.includes(status)) {
newStatuses = statuses.filter((s) => s !== status);
} else {
newStatuses = [...statuses, status];
}
const newStatus = newStatuses.join(" ");
setCurrentToken((prevToken) => ({
...prevToken,
status: newStatus,
}));
onTokenChange({ ...currentToken, status: newStatus });
}
useEffect(() => { useEffect(() => {
function handleTokenMenuOpen(event) { function handleTokenMenuOpen(event) {
const target = event.target; const target = event.target;
@ -64,17 +83,22 @@ function TokenMenu({ tokenClassName, onTokenChange }) {
}; };
}, [tokenClassName]); }, [tokenClassName]);
const { theme } = useThemeUI();
function handleModalContent(node) { function handleModalContent(node) {
if (node) { if (node) {
const tokenLabelInput = node.querySelector("#changeTokenLabel"); const tokenLabelInput = node.querySelector("#changeTokenLabel");
tokenLabelInput.focus(); tokenLabelInput.focus();
// Highlight label section of input
tokenLabelInput.setSelectionRange(7, 8); tokenLabelInput.setSelectionRange(7, 8);
tokenLabelInput.onblur = () => {
setIsOpen(false); // Close modal if interacting with any other element
}; function handlePointerDown(event) {
const path = event.composedPath();
if (!path.includes(node)) {
setIsOpen(false);
document.body.removeEventListener("pointerdown", handlePointerDown);
}
}
document.body.addEventListener("pointerdown", handlePointerDown);
// Check for wheel event to close modal as well // Check for wheel event to close modal as well
document.body.addEventListener( document.body.addEventListener(
"wheel", "wheel",
@ -93,7 +117,7 @@ function TokenMenu({ tokenClassName, onTokenChange }) {
style={{ style={{
overlay: { top: "0", bottom: "initial" }, overlay: { top: "0", bottom: "initial" },
content: { content: {
backgroundColor: theme.colors.highlight, backgroundColor: "hsla(230, 25%, 18%, 0.8)",
top: `${menuTop}px`, top: `${menuTop}px`,
left: `${menuLeft}px`, left: `${menuLeft}px`,
right: "initial", right: "initial",
@ -105,21 +129,63 @@ function TokenMenu({ tokenClassName, onTokenChange }) {
}} }}
contentRef={handleModalContent} contentRef={handleModalContent}
> >
<Box <Box sx={{ width: "80px" }} p={1}>
as="form" <Box
bg="background" as="form"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
handleRequestClose(); handleRequestClose();
}} }}
sx={{ width: "72px" }} >
> <Input
<Input id="changeTokenLabel"
id="changeTokenLabel" onChange={handleLabelChange}
onChange={handleLabelChange} value={`Label: ${currentToken.label}`}
value={`Label: ${currentToken.label}`} sx={{
sx={{ padding: "4px" }} padding: "4px",
/> border: "none",
":focus": {
outline: "none",
},
}}
autoComplete="off"
/>
</Box>
<Box
sx={{
display: "flex",
flexWrap: "wrap",
justifyContent: "space-between",
}}
>
{statusOptions.map((status) => (
<Box
key={status}
sx={{
width: "33%",
paddingTop: "33%",
borderRadius: "50%",
transform: "scale(0.75)",
backgroundColor: statusToColor(status),
cursor: "pointer",
}}
onClick={() => handleStatusChange(status)}
>
{currentToken.status && currentToken.status.includes(status) && (
<Box
sx={{
width: "100%",
height: "100%",
border: "2px solid white",
position: "absolute",
top: 0,
borderRadius: "50%",
}}
/>
)}
</Box>
))}
</Box>
</Box> </Box>
</Modal> </Modal>
); );

View File

@ -0,0 +1,47 @@
import React from "react";
import { Box } from "theme-ui";
import { statusToColor } from "../helpers/status";
function TokenStatus({ statuses }) {
return (
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
pointerEvents: "none",
}}
>
{statuses.map((status, index) => (
<Box
key={status}
sx={{
width: "100%",
height: "100%",
position: "absolute",
opacity: 0.8,
transform: `scale(1.${index + 1})`,
}}
>
<svg
style={{ position: "absolute" }}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
>
<circle
r={47}
cx={50}
cy={50}
fill="none"
stroke={statusToColor(status)}
strokeWidth={4}
/>
</svg>
</Box>
))}
</Box>
);
}
export default TokenStatus;

View File

@ -22,6 +22,7 @@ function Tokens({ onCreateMapToken }) {
id: shortid.generate(), id: shortid.generate(),
size: tokenSize, size: tokenSize,
label: "", label: "",
status: "",
}); });
} }
} }

27
src/helpers/status.js Normal file
View File

@ -0,0 +1,27 @@
export function statusToColor(status) {
switch (status) {
case "blue":
return "rgb(26, 106, 255)";
case "orange":
return "rgb(255, 116, 51)";
case "red":
return "rgb(255, 77, 77)";
case "purple":
return "rgb(136, 77, 255)";
case "green":
return "rgb(133, 255, 102)";
case "pink":
return "rgb(235, 138, 255)";
default:
throw Error("Color not implemented");
}
}
export const statusOptions = [
"blue",
"orange",
"red",
"purple",
"green",
"pink",
];