Converted token label and status to konva
This commit is contained in:
parent
5b70f69fb7
commit
b9968053b7
@ -1,5 +1,5 @@
|
||||
import React, { useContext, useState, useEffect, useRef } from "react";
|
||||
import { Image as KonvaImage } from "react-konva";
|
||||
import { Image as KonvaImage, Group } from "react-konva";
|
||||
import useImage from "use-image";
|
||||
|
||||
import useDataSource from "../../helpers/useDataSource";
|
||||
@ -8,6 +8,9 @@ import useDebounce from "../../helpers/useDebounce";
|
||||
import AuthContext from "../../contexts/AuthContext";
|
||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||
|
||||
import TokenStatus from "../token/TokenStatus";
|
||||
import TokenLabel from "../token/TokenLabel";
|
||||
|
||||
import { tokenSources, unknownSource } from "../../tokens";
|
||||
|
||||
function MapToken({
|
||||
@ -57,32 +60,49 @@ function MapToken({
|
||||
const imageRef = useRef();
|
||||
useEffect(() => {
|
||||
const image = imageRef.current;
|
||||
if (image) {
|
||||
if (image && tokenSourceStatus === "loaded") {
|
||||
image.cache({
|
||||
pixelRatio: debouncedStageScale,
|
||||
});
|
||||
image.drawHitFromCache();
|
||||
// Force redraw
|
||||
image.parent.draw();
|
||||
image.getLayer().draw();
|
||||
}
|
||||
}, [debouncedStageScale, tokenWidth, tokenHeight, tokenSourceStatus]);
|
||||
|
||||
return (
|
||||
<KonvaImage
|
||||
ref={imageRef}
|
||||
<Group
|
||||
width={tokenWidth}
|
||||
height={tokenHeight}
|
||||
x={tokenState.x * mapWidth}
|
||||
y={tokenState.y * mapHeight}
|
||||
image={tokenSourceImage}
|
||||
draggable
|
||||
onDragEnd={handleDragEnd}
|
||||
onClick={handleClick}
|
||||
onMouseDown={() => setPreventMapInteraction(true)}
|
||||
onMouseUp={() => setPreventMapInteraction(false)}
|
||||
onTouchStart={() => setPreventMapInteraction(true)}
|
||||
onTouchEnd={() => setPreventMapInteraction(false)}
|
||||
onClick={handleClick}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<KonvaImage
|
||||
ref={imageRef}
|
||||
width={tokenWidth}
|
||||
height={tokenHeight}
|
||||
x={0}
|
||||
y={0}
|
||||
image={tokenSourceImage}
|
||||
/>
|
||||
<TokenStatus
|
||||
tokenState={tokenState}
|
||||
width={tokenWidth}
|
||||
height={tokenHeight}
|
||||
/>
|
||||
<TokenLabel
|
||||
tokenState={tokenState}
|
||||
width={tokenWidth}
|
||||
height={tokenHeight}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,6 @@ import interact from "interactjs";
|
||||
|
||||
import usePortal from "../../helpers/usePortal";
|
||||
|
||||
import TokenLabel from "./TokenLabel";
|
||||
import TokenStatus from "./TokenStatus";
|
||||
|
||||
import MapStageContext from "../../contexts/MapStageContext";
|
||||
|
||||
/**
|
||||
@ -21,15 +18,9 @@ import MapStageContext from "../../contexts/MapStageContext";
|
||||
* @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
|
||||
* @param {Object} disabledTokens An optional mapping of tokens that shouldn't allow movement
|
||||
|
||||
*/
|
||||
function ProxyToken({
|
||||
tokenClassName,
|
||||
onProxyDragEnd,
|
||||
tokens,
|
||||
disabledTokens,
|
||||
}) {
|
||||
function ProxyToken({ tokenClassName, onProxyDragEnd, tokens }) {
|
||||
const proxyContainer = usePortal("root");
|
||||
|
||||
const [imageSource, setImageSource] = useState("");
|
||||
@ -39,11 +30,9 @@ function ProxyToken({
|
||||
// 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);
|
||||
const disabledTokensRef = useRef(disabledTokens);
|
||||
useEffect(() => {
|
||||
tokensRef.current = tokens;
|
||||
disabledTokensRef.current = disabledTokens;
|
||||
}, [tokens, disabledTokens]);
|
||||
}, [tokens]);
|
||||
|
||||
const proxyOnMap = useRef(false);
|
||||
const mapStageRef = useContext(MapStageContext);
|
||||
@ -54,9 +43,6 @@ function ProxyToken({
|
||||
start: (event) => {
|
||||
let target = event.target;
|
||||
const id = target.dataset.id;
|
||||
if (id in disabledTokensRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide the token and copy it's image to the proxy
|
||||
target.parentElement.style.opacity = "0.25";
|
||||
@ -108,9 +94,6 @@ function ProxyToken({
|
||||
end: (event) => {
|
||||
let target = event.target;
|
||||
const id = target.dataset.id;
|
||||
if (id in disabledTokensRef.current) {
|
||||
return;
|
||||
}
|
||||
let proxy = proxyRef.current;
|
||||
if (proxy) {
|
||||
const mapStage = mapStageRef.current;
|
||||
@ -187,12 +170,6 @@ function ProxyToken({
|
||||
width: "100%",
|
||||
}}
|
||||
/>
|
||||
{tokens[tokenId] && tokens[tokenId].statuses && (
|
||||
<TokenStatus token={tokens[tokenId]} />
|
||||
)}
|
||||
{tokens[tokenId] && tokens[tokenId].label && (
|
||||
<TokenLabel token={tokens[tokenId]} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>,
|
||||
proxyContainer
|
||||
@ -201,7 +178,6 @@ function ProxyToken({
|
||||
|
||||
ProxyToken.defaultProps = {
|
||||
tokens: {},
|
||||
disabledTokens: {},
|
||||
};
|
||||
|
||||
export default ProxyToken;
|
||||
|
@ -1,60 +1,49 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "theme-ui";
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { Rect, Text, Group } from "react-konva";
|
||||
|
||||
function TokenLabel({ tokenState, width, height }) {
|
||||
const fontSize = height / 6 / tokenState.size;
|
||||
const paddingY = height / 16 / tokenState.size;
|
||||
const paddingX = height / 8 / tokenState.size;
|
||||
|
||||
const [rectWidth, setRectWidth] = useState(0);
|
||||
useEffect(() => {
|
||||
const text = textRef.current;
|
||||
if (text && tokenState.label) {
|
||||
setRectWidth(text.getTextWidth() + paddingX);
|
||||
} else {
|
||||
setRectWidth(0);
|
||||
}
|
||||
}, [tokenState.label, paddingX]);
|
||||
|
||||
const textRef = useRef();
|
||||
|
||||
function TokenLabel({ token }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
transform: `scale(${0.3 / token.size}) translate(0, 20%)`,
|
||||
transformOrigin: "bottom center",
|
||||
pointerEvents: "none",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{/* Use SVG so text is scaled with token size */}
|
||||
<svg
|
||||
viewBox="0 0 100 100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ overflow: "visible" }}
|
||||
>
|
||||
<foreignObject
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ overflow: "visible" }}
|
||||
>
|
||||
<Box sx={{ width: "100%", height: "100%", position: "relative" }}>
|
||||
<Group y={height - (fontSize + paddingY) / 2}>
|
||||
<Rect
|
||||
y={-paddingY / 2}
|
||||
width={rectWidth}
|
||||
offsetX={width / 2}
|
||||
x={width - rectWidth / 2}
|
||||
height={fontSize + paddingY}
|
||||
fill="hsla(230, 25%, 18%, 0.8)"
|
||||
cornerRadius={(fontSize + paddingY) / 2}
|
||||
/>
|
||||
<Text
|
||||
as="p"
|
||||
variant="heading"
|
||||
sx={{
|
||||
fontSize: "66px",
|
||||
textAlign: "center",
|
||||
verticalAlign: "middle",
|
||||
lineHeight: 1.4,
|
||||
whiteSpace: "nowrap",
|
||||
minWidth: "100%",
|
||||
display: "inline-block",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
borderRadius: "66px",
|
||||
border: "2px solid",
|
||||
borderColor: "muted",
|
||||
}}
|
||||
bg="overlay"
|
||||
px={4}
|
||||
>
|
||||
{token.label}
|
||||
</Text>
|
||||
</Box>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
</Box>
|
||||
ref={textRef}
|
||||
width={width}
|
||||
text={tokenState.label}
|
||||
fontSize={fontSize}
|
||||
lineHeight={1}
|
||||
align="center"
|
||||
verticalAlign="bottom"
|
||||
fill="white"
|
||||
paddingX={paddingX}
|
||||
paddingY={paddingY}
|
||||
wrap="none"
|
||||
ellipsis={true}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,46 +1,23 @@
|
||||
import React from "react";
|
||||
import { Box } from "theme-ui";
|
||||
import { Circle, Group } from "react-konva";
|
||||
|
||||
import colors from "../../helpers/colors";
|
||||
|
||||
function TokenStatus({ token }) {
|
||||
function TokenStatus({ tokenState, width, height }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
{token.statuses.map((status, index) => (
|
||||
<Box
|
||||
key={status}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
opacity: 0.8,
|
||||
transform: `scale(${1 - index / 10 / token.size})`,
|
||||
}}
|
||||
>
|
||||
<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"
|
||||
<Group x={width} y={height} offsetX={width / 2} offsetY={height / 2}>
|
||||
{tokenState.statuses.map((status, index) => (
|
||||
<Circle
|
||||
width={width}
|
||||
height={height}
|
||||
stroke={colors[status]}
|
||||
strokeWidth={4 / token.size}
|
||||
strokeWidth={width / 20 / tokenState.size}
|
||||
scaleX={1 - index / 10 / tokenState.size}
|
||||
scaleY={1 - index / 10 / tokenState.size}
|
||||
opacity={0.8}
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user