Added custom sizing support
This commit is contained in:
parent
43aaf04843
commit
3bd8fce6ab
@ -17,7 +17,7 @@ function Map({
|
||||
tokens,
|
||||
onMapTokenMove,
|
||||
onMapTokenRemove,
|
||||
onMapChanged
|
||||
onMapChanged,
|
||||
}) {
|
||||
function handleProxyDragEnd(isOnMap, token) {
|
||||
if (isOnMap && onMapTokenMove) {
|
||||
@ -36,29 +36,29 @@ function Map({
|
||||
interact(".map")
|
||||
.gesturable({
|
||||
listeners: {
|
||||
move: event => {
|
||||
setMapScale(previousMapScale =>
|
||||
move: (event) => {
|
||||
setMapScale((previousMapScale) =>
|
||||
Math.max(Math.min(previousMapScale + event.ds, maxZoom), minZoom)
|
||||
);
|
||||
setMapTranslate(previousMapTranslate => ({
|
||||
setMapTranslate((previousMapTranslate) => ({
|
||||
x: previousMapTranslate.x + event.dx,
|
||||
y: previousMapTranslate.y + event.dy
|
||||
y: previousMapTranslate.y + event.dy,
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
.draggable({
|
||||
inertia: true,
|
||||
listeners: {
|
||||
move: event => {
|
||||
setMapTranslate(previousMapTranslate => ({
|
||||
move: (event) => {
|
||||
setMapTranslate((previousMapTranslate) => ({
|
||||
x: previousMapTranslate.x + event.dx,
|
||||
y: previousMapTranslate.y + event.dy
|
||||
y: previousMapTranslate.y + event.dy,
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
interact(".map").on("doubletap", event => {
|
||||
interact(".map").on("doubletap", (event) => {
|
||||
event.preventDefault();
|
||||
setMapTranslate({ x: 0, y: 0 });
|
||||
setMapScale(1);
|
||||
@ -67,7 +67,7 @@ function Map({
|
||||
|
||||
function handleZoom(event) {
|
||||
const deltaY = event.deltaY * zoomSpeed;
|
||||
setMapScale(previousMapScale =>
|
||||
setMapScale((previousMapScale) =>
|
||||
Math.max(Math.min(previousMapScale + deltaY, maxZoom), minZoom)
|
||||
);
|
||||
}
|
||||
@ -93,7 +93,7 @@ function Map({
|
||||
overflow: "hidden",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
||||
userSelect: "none",
|
||||
touchAction: "none"
|
||||
touchAction: "none",
|
||||
}}
|
||||
bg="background"
|
||||
onWheel={handleZoom}
|
||||
@ -103,19 +103,19 @@ function Map({
|
||||
position: "relative",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)"
|
||||
transform: "translate(-50%, -50%)",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
transform: `translate(${mapTranslate.x}px, ${mapTranslate.y}px) scale(${mapScale})`
|
||||
transform: `translate(${mapTranslate.x}px, ${mapTranslate.y}px) scale(${mapScale})`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: 0,
|
||||
paddingBottom: `${(1 / aspectRatio) * 100}%`
|
||||
paddingBottom: `${(1 / aspectRatio) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
@ -124,7 +124,7 @@ function Map({
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
@ -133,7 +133,7 @@ function Map({
|
||||
sx={{
|
||||
width: "100%",
|
||||
userSelect: "none",
|
||||
touchAction: "none"
|
||||
touchAction: "none",
|
||||
}}
|
||||
src={mapSource}
|
||||
/>
|
||||
@ -144,24 +144,27 @@ function Map({
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
{Object.values(tokens).map(token => (
|
||||
{Object.values(tokens).map((token) => (
|
||||
<Box
|
||||
key={token.id}
|
||||
style={{
|
||||
left: `${token.x * 100}%`,
|
||||
top: `${token.y * 100}%`,
|
||||
width: `${tokenSizePercent}%`
|
||||
width: `${tokenSizePercent * (token.size || 1)}%`,
|
||||
}}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
display: "flex"
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Token
|
||||
tokenId={token.id}
|
||||
data={{
|
||||
id: token.id,
|
||||
size: token.size,
|
||||
}}
|
||||
image={token.image}
|
||||
className={mapTokenClassName}
|
||||
sx={{ position: "absolute" }}
|
||||
@ -177,7 +180,7 @@ function Map({
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)"
|
||||
transform: "translateX(-50%)",
|
||||
}}
|
||||
>
|
||||
<AddMapButton onMapChanged={onMapChanged} />
|
||||
|
@ -16,7 +16,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
||||
useEffect(() => {
|
||||
interact(`.${tokenClassName}`).draggable({
|
||||
listeners: {
|
||||
start: event => {
|
||||
start: (event) => {
|
||||
let target = event.target;
|
||||
// Hide the token and copy it's image to the proxy
|
||||
target.style.opacity = "0.25";
|
||||
@ -39,7 +39,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
||||
}
|
||||
},
|
||||
|
||||
move: event => {
|
||||
move: (event) => {
|
||||
let proxy = imageRef.current;
|
||||
// Move the proxy based off of the movment of the token
|
||||
if (proxy) {
|
||||
@ -66,7 +66,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
||||
}
|
||||
},
|
||||
|
||||
end: event => {
|
||||
end: (event) => {
|
||||
let target = event.target;
|
||||
let proxy = imageRef.current;
|
||||
if (proxy) {
|
||||
@ -85,12 +85,12 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
||||
x = x / (mapRect.right - mapRect.left);
|
||||
y = y / (mapRect.bottom - mapRect.top);
|
||||
|
||||
const id = target.getAttribute("data-token-id");
|
||||
onProxyDragEnd(proxyOnMap.current, {
|
||||
image: imageSource,
|
||||
x,
|
||||
y,
|
||||
id
|
||||
// Pass in props stored as data- in the dom node
|
||||
...target.dataset,
|
||||
});
|
||||
}
|
||||
|
||||
@ -103,8 +103,8 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
||||
// Show the token
|
||||
target.style.opacity = "1";
|
||||
setImageSource("");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [imageSource, onProxyDragEnd, tokenClassName, proxyContainer]);
|
||||
|
||||
@ -121,7 +121,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0
|
||||
right: 0,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
@ -129,7 +129,7 @@ function ProxyToken({ tokenClassName, onProxyDragEnd }) {
|
||||
sx={{
|
||||
touchAction: "none",
|
||||
userSelect: "none",
|
||||
position: "absolute"
|
||||
position: "absolute",
|
||||
}}
|
||||
ref={imageRef}
|
||||
/>
|
||||
|
52
src/components/SizeInput.js
Normal file
52
src/components/SizeInput.js
Normal file
@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
import { Box, Flex, IconButton, Text, Label } from "theme-ui";
|
||||
|
||||
function SizeInput({ value, onChange }) {
|
||||
return (
|
||||
<Box>
|
||||
<Text sx={{ textAlign: "center" }} variant="heading">
|
||||
Size
|
||||
</Text>
|
||||
<Flex sx={{ alignItems: "center", justifyContent: "center" }}>
|
||||
<IconButton
|
||||
aria-label="Lower token size"
|
||||
onClick={() => value > 1 && onChange(value - 1)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
fill="currentcolor"
|
||||
>
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M18 13H6c-.55 0-1-.45-1-1s.45-1 1-1h12c.55 0 1 .45 1 1s-.45 1-1 1z" />
|
||||
</svg>
|
||||
</IconButton>
|
||||
<Text>{value}</Text>
|
||||
<IconButton
|
||||
aria-label="Increase token size"
|
||||
onClick={() => onChange(value + 1)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
fill="currentcolor"
|
||||
>
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M18 13h-5v5c0 .55-.45 1-1 1s-1-.45-1-1v-5H6c-.55 0-1-.45-1-1s.45-1 1-1h5V6c0-.55.45-1 1-1s1 .45 1 1v5h5c.55 0 1 .45 1 1s-.45 1-1 1z" />
|
||||
</svg>
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
SizeInput.defaultProps = {
|
||||
value: 1,
|
||||
onChange: () => {},
|
||||
};
|
||||
|
||||
export default SizeInput;
|
@ -1,21 +1,26 @@
|
||||
import React from "react";
|
||||
import { Image } from "theme-ui";
|
||||
|
||||
function Token({ image, className, tokenId, sx }) {
|
||||
// Store the token id in the html element for the drag code to pick it up
|
||||
const idProp = tokenId ? { "data-token-id": tokenId } : {};
|
||||
// The data prop is used to pass data into the dom element
|
||||
// this can be used to pass state to the ProxyToken
|
||||
function Token({ image, className, data, sx }) {
|
||||
// Map the keys in data to have the `data-` prefix
|
||||
const dataProps = Object.fromEntries(
|
||||
Object.entries(data).map(([key, value]) => [`data-${key}`, value])
|
||||
);
|
||||
return (
|
||||
<Image
|
||||
className={className}
|
||||
sx={{ userSelect: "none", touchAction: "none", ...sx }}
|
||||
src={image}
|
||||
{...idProp}
|
||||
{...dataProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Token.defaultProps = {
|
||||
sx: {}
|
||||
data: {},
|
||||
sx: {},
|
||||
};
|
||||
|
||||
export default Token;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Flex, Box } from "theme-ui";
|
||||
import shortid from "shortid";
|
||||
|
||||
@ -6,34 +6,47 @@ import * as tokens from "../tokens";
|
||||
|
||||
import Token from "./Token";
|
||||
import ProxyToken from "./ProxyToken";
|
||||
import SizeInput from "./SizeInput";
|
||||
|
||||
const listTokenClassName = "list-token";
|
||||
|
||||
function Tokens({ onCreateMapToken }) {
|
||||
const [tokenSize, setTokenSize] = useState(1);
|
||||
|
||||
function handleProxyDragEnd(isOnMap, token) {
|
||||
if (isOnMap && onCreateMapToken) {
|
||||
// Give the token an id
|
||||
onCreateMapToken({ ...token, id: shortid.generate() });
|
||||
onCreateMapToken({ ...token, id: shortid.generate(), size: tokenSize });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
bg="background"
|
||||
sx={{
|
||||
width: "80px",
|
||||
minWidth: "80px",
|
||||
flexDirection: "column",
|
||||
overflowY: "auto"
|
||||
}}
|
||||
px={2}
|
||||
>
|
||||
{Object.entries(tokens).map(([id, image]) => (
|
||||
<Box key={id} m={2} sx={{ width: "48px", height: "48px" }}>
|
||||
<Token image={image} className={listTokenClassName} />
|
||||
</Box>
|
||||
))}
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Flex
|
||||
bg="background"
|
||||
sx={{
|
||||
width: "80px",
|
||||
minWidth: "80px",
|
||||
flexDirection: "column",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
px={2}
|
||||
>
|
||||
{Object.entries(tokens).map(([id, image]) => (
|
||||
<Box key={id} m={2} sx={{ width: "48px", height: "48px" }}>
|
||||
<Token image={image} className={listTokenClassName} />
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
<Box
|
||||
pt={1}
|
||||
sx={{
|
||||
backgroundColor: "muted",
|
||||
}}
|
||||
>
|
||||
<SizeInput value={tokenSize} onChange={setTokenSize} />
|
||||
</Box>
|
||||
</Flex>
|
||||
<ProxyToken
|
||||
tokenClassName={listTokenClassName}
|
||||
|
Loading…
Reference in New Issue
Block a user