grungnet/src/components/token/TokenPreview.js
2021-04-22 16:53:35 +10:00

157 lines
4.5 KiB
JavaScript

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 "../../hooks/usePreventOverscroll";
import useStageInteraction from "../../hooks/useStageInteraction";
import useImageCenter from "../../hooks/useImageCenter";
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
import { GridProvider } from "../../contexts/GridContext";
import { useDataURL } from "../../contexts/AssetsContext";
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 tokenURL = useDataURL(tokenSourceData, tokenSources, unknownSource);
const [tokenSourceImage] = useImage(tokenURL);
const [stageWidth, setStageWidth] = useState(1);
const [stageHeight, setStageHeight] = useState(1);
const [stageScale, setStageScale] = useState(1);
const stageTranslateRef = useRef({ x: 0, y: 0 });
const tokenStageRef = useRef();
const tokenLayerRef = useRef();
function handleResize(width, height) {
setStageWidth(width);
setStageHeight(height);
}
const containerRef = useRef();
usePreventOverscroll(containerRef);
const [tokenWidth, tokenHeight] = useImageCenter(
token,
tokenStageRef,
stageWidth,
stageHeight,
stageTranslateRef,
setStageScale,
tokenLayerRef,
containerRef,
true
);
useStageInteraction(
tokenStageRef.current,
stageScale,
setStageScale,
stageTranslateRef,
tokenLayerRef.current
);
const [showGridPreview, setShowGridPreview] = useState(true);
const gridWidth = tokenWidth;
const gridX = token.defaultSize;
const gridSize = gridWidth / gridX;
const gridY = Math.round(tokenHeight / gridSize);
const gridHeight = gridY > 0 ? gridY * gridSize : tokenHeight;
const borderWidth = Math.max(
(Math.min(tokenWidth, gridHeight) / 200) * Math.max(1 / stageScale, 1),
1
);
const layout = useResponsiveLayout();
return (
<Box
sx={{
width: "100%",
height: layout.screenSize === "large" ? "500px" : "300px",
cursor: "move",
touchAction: "none",
outline: "none",
position: "relative",
}}
bg="muted"
ref={containerRef}
>
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
<Stage
width={stageWidth}
height={stageHeight}
scale={{ x: stageScale, y: stageScale }}
ref={tokenStageRef}
>
<Layer ref={tokenLayerRef}>
<Image
image={tokenSourceImage}
width={tokenWidth}
height={tokenHeight}
/>
{showGridPreview && (
<Group offsetY={gridHeight - tokenHeight}>
<GridProvider
grid={{
size: { x: gridX, y: gridY },
inset: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: 1, y: 1 },
},
type: "square",
}}
width={gridWidth}
height={gridHeight}
>
<Grid />
</GridProvider>
<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;