Added token status rings
This commit is contained in:
parent
8ba3881529
commit
441e589ec3
@ -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>
|
||||||
|
@ -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>,
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
47
src/components/TokenStatus.js
Normal file
47
src/components/TokenStatus.js
Normal 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;
|
@ -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
27
src/helpers/status.js
Normal 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",
|
||||||
|
];
|
Loading…
Reference in New Issue
Block a user