Added token preview to edit token modal and refactored Grid from Map Grid
This commit is contained in:
parent
0235bdb7ef
commit
e9e7794027
72
src/components/Grid.js
Normal file
72
src/components/Grid.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Line, Group } from "react-konva";
|
||||||
|
|
||||||
|
import { getStrokeWidth } from "../helpers/drawing";
|
||||||
|
|
||||||
|
function Grid({ gridX, gridY, gridInset, strokeWidth, width, height, stroke }) {
|
||||||
|
if (!gridX || !gridY) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridSizeNormalized = {
|
||||||
|
x: (gridInset.bottomRight.x - gridInset.topLeft.x) / gridX,
|
||||||
|
y: (gridInset.bottomRight.y - gridInset.topLeft.y) / gridY,
|
||||||
|
};
|
||||||
|
|
||||||
|
const insetWidth = (gridInset.bottomRight.x - gridInset.topLeft.x) * width;
|
||||||
|
const insetHeight = (gridInset.bottomRight.y - gridInset.topLeft.y) * height;
|
||||||
|
|
||||||
|
const lineSpacingX = insetWidth / gridX;
|
||||||
|
const lineSpacingY = insetHeight / gridY;
|
||||||
|
|
||||||
|
const offsetX = gridInset.topLeft.x * width * -1;
|
||||||
|
const offsetY = gridInset.topLeft.y * height * -1;
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
for (let x = 1; x < gridX; x++) {
|
||||||
|
lines.push(
|
||||||
|
<Line
|
||||||
|
key={`grid_x_${x}`}
|
||||||
|
points={[x * lineSpacingX, 0, x * lineSpacingX, insetHeight]}
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={getStrokeWidth(
|
||||||
|
strokeWidth,
|
||||||
|
gridSizeNormalized,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
)}
|
||||||
|
opacity={0.5}
|
||||||
|
offsetX={offsetX}
|
||||||
|
offsetY={offsetY}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (let y = 1; y < gridY; y++) {
|
||||||
|
lines.push(
|
||||||
|
<Line
|
||||||
|
key={`grid_y_${y}`}
|
||||||
|
points={[0, y * lineSpacingY, insetWidth, y * lineSpacingY]}
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={getStrokeWidth(
|
||||||
|
strokeWidth,
|
||||||
|
gridSizeNormalized,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
)}
|
||||||
|
opacity={0.5}
|
||||||
|
offsetX={offsetX}
|
||||||
|
offsetY={offsetY}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Group>{lines}</Group>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Grid.defaultProps = {
|
||||||
|
strokeWidth: 0.1,
|
||||||
|
gridInset: { topLeft: { x: 0, y: 0 }, bottomRight: { x: 1, y: 1 } },
|
||||||
|
stroke: "white",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Grid;
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import { Line, Group } from "react-konva";
|
|
||||||
import useImage from "use-image";
|
import useImage from "use-image";
|
||||||
|
|
||||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||||
@ -7,9 +6,10 @@ import MapInteractionContext from "../../contexts/MapInteractionContext";
|
|||||||
import useDataSource from "../../helpers/useDataSource";
|
import useDataSource from "../../helpers/useDataSource";
|
||||||
import { mapSources as defaultMapSources } from "../../maps";
|
import { mapSources as defaultMapSources } from "../../maps";
|
||||||
|
|
||||||
import { getStrokeWidth } from "../../helpers/drawing";
|
|
||||||
import { getImageLightness } from "../../helpers/image";
|
import { getImageLightness } from "../../helpers/image";
|
||||||
|
|
||||||
|
import Grid from "../Grid";
|
||||||
|
|
||||||
function MapGrid({ map, strokeWidth }) {
|
function MapGrid({ map, strokeWidth }) {
|
||||||
const { mapWidth, mapHeight } = useContext(MapInteractionContext);
|
const { mapWidth, mapHeight } = useContext(MapInteractionContext);
|
||||||
|
|
||||||
@ -36,66 +36,19 @@ function MapGrid({ map, strokeWidth }) {
|
|||||||
const gridX = map && map.grid.size.x;
|
const gridX = map && map.grid.size.x;
|
||||||
const gridY = map && map.grid.size.y;
|
const gridY = map && map.grid.size.y;
|
||||||
|
|
||||||
if (!gridX || !gridY) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gridInset = map && map.grid.inset;
|
const gridInset = map && map.grid.inset;
|
||||||
|
|
||||||
const gridSizeNormalized = {
|
return (
|
||||||
x: (gridInset.bottomRight.x - gridInset.topLeft.x) / gridX,
|
<Grid
|
||||||
y: (gridInset.bottomRight.y - gridInset.topLeft.y) / gridY,
|
gridX={gridX}
|
||||||
};
|
gridY={gridY}
|
||||||
|
gridInset={gridInset}
|
||||||
const insetWidth = (gridInset.bottomRight.x - gridInset.topLeft.x) * mapWidth;
|
strokeWidth={strokeWidth}
|
||||||
const insetHeight =
|
width={mapWidth}
|
||||||
(gridInset.bottomRight.y - gridInset.topLeft.y) * mapHeight;
|
height={mapHeight}
|
||||||
|
stroke={isImageLight ? "black" : "white"}
|
||||||
const lineSpacingX = insetWidth / gridX;
|
/>
|
||||||
const lineSpacingY = insetHeight / gridY;
|
);
|
||||||
|
|
||||||
const offsetX = gridInset.topLeft.x * mapWidth * -1;
|
|
||||||
const offsetY = gridInset.topLeft.y * mapHeight * -1;
|
|
||||||
|
|
||||||
const lines = [];
|
|
||||||
for (let x = 1; x < gridX; x++) {
|
|
||||||
lines.push(
|
|
||||||
<Line
|
|
||||||
key={`grid_x_${x}`}
|
|
||||||
points={[x * lineSpacingX, 0, x * lineSpacingX, insetHeight]}
|
|
||||||
stroke={isImageLight ? "black" : "white"}
|
|
||||||
strokeWidth={getStrokeWidth(
|
|
||||||
strokeWidth,
|
|
||||||
gridSizeNormalized,
|
|
||||||
mapWidth,
|
|
||||||
mapHeight
|
|
||||||
)}
|
|
||||||
opacity={0.5}
|
|
||||||
offsetX={offsetX}
|
|
||||||
offsetY={offsetY}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (let y = 1; y < gridY; y++) {
|
|
||||||
lines.push(
|
|
||||||
<Line
|
|
||||||
key={`grid_y_${y}`}
|
|
||||||
points={[0, y * lineSpacingY, insetWidth, y * lineSpacingY]}
|
|
||||||
stroke={isImageLight ? "black" : "white"}
|
|
||||||
strokeWidth={getStrokeWidth(
|
|
||||||
strokeWidth,
|
|
||||||
gridSizeNormalized,
|
|
||||||
mapWidth,
|
|
||||||
mapHeight
|
|
||||||
)}
|
|
||||||
opacity={0.5}
|
|
||||||
offsetX={offsetX}
|
|
||||||
offsetY={offsetY}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Group>{lines}</Group>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MapGrid.defaultProps = {
|
MapGrid.defaultProps = {
|
||||||
|
186
src/components/token/TokenPreview.js
Normal file
186
src/components/token/TokenPreview.js
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
import { Box, IconButton } from "theme-ui";
|
||||||
|
import { Stage, Layer, Image, Rect, Group } from "react-konva";
|
||||||
|
import ReactResizeDetector from "react-resize-detector";
|
||||||
|
import useImage from "use-image";
|
||||||
|
|
||||||
|
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
||||||
|
import useStageInteraction from "../../helpers/useStageInteraction";
|
||||||
|
import useDataSource from "../../helpers/useDataSource";
|
||||||
|
|
||||||
|
import GridOnIcon from "../../icons/GridOnIcon";
|
||||||
|
import GridOffIcon from "../../icons/GridOffIcon";
|
||||||
|
|
||||||
|
import { tokenSources, unknownSource } from "../../tokens";
|
||||||
|
|
||||||
|
import Grid from "../Grid";
|
||||||
|
|
||||||
|
function TokenPreview({ token }) {
|
||||||
|
const [tokenSourceData, setTokenSourceData] = useState({});
|
||||||
|
useEffect(() => {
|
||||||
|
if (token.id !== tokenSourceData.id) {
|
||||||
|
setTokenSourceData(token);
|
||||||
|
}
|
||||||
|
}, [token, tokenSourceData]);
|
||||||
|
|
||||||
|
const tokenSource = useDataSource(
|
||||||
|
tokenSourceData,
|
||||||
|
tokenSources,
|
||||||
|
unknownSource
|
||||||
|
);
|
||||||
|
const [tokenSourceImage] = useImage(tokenSource);
|
||||||
|
const [tokenRatio, setTokenRatio] = useState(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tokenSourceImage) {
|
||||||
|
setTokenRatio(tokenSourceImage.width / tokenSourceImage.height);
|
||||||
|
}
|
||||||
|
}, [tokenSourceImage]);
|
||||||
|
|
||||||
|
const [stageWidth, setStageWidth] = useState(1);
|
||||||
|
const [stageHeight, setStageHeight] = useState(1);
|
||||||
|
const [stageScale, setStageScale] = useState(1);
|
||||||
|
|
||||||
|
const stageRatio = stageWidth / stageHeight;
|
||||||
|
|
||||||
|
let tokenWidth;
|
||||||
|
let tokenHeight;
|
||||||
|
if (stageRatio > tokenRatio) {
|
||||||
|
tokenWidth = tokenSourceImage
|
||||||
|
? stageHeight / (tokenSourceImage.height / tokenSourceImage.width)
|
||||||
|
: stageWidth;
|
||||||
|
tokenHeight = stageHeight;
|
||||||
|
} else {
|
||||||
|
tokenWidth = stageWidth;
|
||||||
|
tokenHeight = tokenSourceImage
|
||||||
|
? stageWidth * (tokenSourceImage.height / tokenSourceImage.width)
|
||||||
|
: stageHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
||||||
|
const mapLayerRef = useRef();
|
||||||
|
|
||||||
|
function handleResize(width, height) {
|
||||||
|
setStageWidth(width);
|
||||||
|
setStageHeight(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset map translate and scale
|
||||||
|
useEffect(() => {
|
||||||
|
const layer = mapLayerRef.current;
|
||||||
|
const containerRect = containerRef.current.getBoundingClientRect();
|
||||||
|
if (layer) {
|
||||||
|
let newTranslate;
|
||||||
|
if (stageRatio > tokenRatio) {
|
||||||
|
newTranslate = {
|
||||||
|
x: -(tokenWidth - containerRect.width) / 2,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newTranslate = {
|
||||||
|
x: 0,
|
||||||
|
y: -(tokenHeight - containerRect.height) / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.x(newTranslate.x);
|
||||||
|
layer.y(newTranslate.y);
|
||||||
|
layer.draw();
|
||||||
|
stageTranslateRef.current = newTranslate;
|
||||||
|
|
||||||
|
setStageScale(1);
|
||||||
|
}
|
||||||
|
}, [token.id, tokenWidth, tokenHeight, stageRatio, tokenRatio]);
|
||||||
|
|
||||||
|
const bind = useStageInteraction(
|
||||||
|
mapLayerRef.current,
|
||||||
|
stageScale,
|
||||||
|
setStageScale,
|
||||||
|
stageTranslateRef,
|
||||||
|
"pan"
|
||||||
|
);
|
||||||
|
|
||||||
|
const containerRef = useRef();
|
||||||
|
usePreventOverscroll(containerRef);
|
||||||
|
|
||||||
|
const [showGridPreview, setShowGridPreview] = useState(true);
|
||||||
|
const gridWidth = tokenWidth;
|
||||||
|
const gridX = token.defaultSize;
|
||||||
|
const gridSize = gridWidth / gridX;
|
||||||
|
const gridY = Math.ceil(tokenHeight / gridSize);
|
||||||
|
const gridHeight = gridY > 0 ? gridY * gridSize : tokenHeight;
|
||||||
|
const borderWidth = Math.max(
|
||||||
|
(Math.min(tokenWidth, gridHeight) / 200) * Math.max(1 / stageScale, 1),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "300px",
|
||||||
|
cursor: "move",
|
||||||
|
touchAction: "none",
|
||||||
|
outline: "none",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
bg="muted"
|
||||||
|
ref={containerRef}
|
||||||
|
{...bind()}
|
||||||
|
>
|
||||||
|
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
|
||||||
|
<Stage
|
||||||
|
width={stageWidth}
|
||||||
|
height={stageHeight}
|
||||||
|
scale={{ x: stageScale, y: stageScale }}
|
||||||
|
x={stageWidth / 2}
|
||||||
|
y={stageHeight / 2}
|
||||||
|
offset={{ x: stageWidth / 2, y: stageHeight / 2 }}
|
||||||
|
>
|
||||||
|
<Layer ref={mapLayerRef}>
|
||||||
|
<Image
|
||||||
|
image={tokenSourceImage}
|
||||||
|
width={tokenWidth}
|
||||||
|
height={tokenHeight}
|
||||||
|
/>
|
||||||
|
{showGridPreview && (
|
||||||
|
<Group offsetY={gridHeight - tokenHeight}>
|
||||||
|
<Grid
|
||||||
|
gridX={gridX}
|
||||||
|
gridY={gridY}
|
||||||
|
width={gridWidth}
|
||||||
|
height={gridHeight}
|
||||||
|
/>
|
||||||
|
<Rect
|
||||||
|
width={gridWidth}
|
||||||
|
height={gridHeight}
|
||||||
|
fill="transparent"
|
||||||
|
stroke="rgba(255, 255, 255, 0.75)"
|
||||||
|
strokeWidth={borderWidth}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Layer>
|
||||||
|
</Stage>
|
||||||
|
</ReactResizeDetector>
|
||||||
|
<IconButton
|
||||||
|
title={showGridPreview ? "Hide Grid Preview" : "Show Grid Preview"}
|
||||||
|
aria-label={showGridPreview ? "Hide Grid Preview" : "Show Grid Preview"}
|
||||||
|
onClick={() => setShowGridPreview(!showGridPreview)}
|
||||||
|
bg="overlay"
|
||||||
|
sx={{
|
||||||
|
borderRadius: "50%",
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
}}
|
||||||
|
m={2}
|
||||||
|
p="6px"
|
||||||
|
>
|
||||||
|
{showGridPreview ? <GridOnIcon /> : <GridOffIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenPreview;
|
@ -3,6 +3,7 @@ import { Button, Flex, Label } from "theme-ui";
|
|||||||
|
|
||||||
import Modal from "../components/Modal";
|
import Modal from "../components/Modal";
|
||||||
import TokenSettings from "../components/token/TokenSettings";
|
import TokenSettings from "../components/token/TokenSettings";
|
||||||
|
import TokenPreview from "../components/token/TokenPreview";
|
||||||
|
|
||||||
import TokenDataContext from "../contexts/TokenDataContext";
|
import TokenDataContext from "../contexts/TokenDataContext";
|
||||||
|
|
||||||
@ -62,6 +63,7 @@ function EditTokenModal({ isOpen, onDone, token }) {
|
|||||||
<Label pt={2} pb={1}>
|
<Label pt={2} pb={1}>
|
||||||
Edit token
|
Edit token
|
||||||
</Label>
|
</Label>
|
||||||
|
<TokenPreview token={selectedTokenWithChanges} />
|
||||||
<TokenSettings
|
<TokenSettings
|
||||||
token={selectedTokenWithChanges}
|
token={selectedTokenWithChanges}
|
||||||
onSettingsChange={handleTokenSettingsChange}
|
onSettingsChange={handleTokenSettingsChange}
|
||||||
|
Loading…
Reference in New Issue
Block a user