Typescript

This commit is contained in:
Mitchell McCaffrey 2021-07-16 21:39:45 +10:00
parent 74cabdd798
commit e48d19a817
14 changed files with 81 additions and 50 deletions

View File

@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Box, IconButton } from "theme-ui"; import { Box, IconButton } from "theme-ui";
import { Node } from "konva/types/Node"; import Konva from "konva";
import RemoveTokenIcon from "../../icons/RemoveTokenIcon"; import RemoveTokenIcon from "../../icons/RemoveTokenIcon";
type DragOverlayProps = { type DragOverlayProps = {
dragging: boolean; dragging: boolean;
node: Node; node: Konva.Node;
onRemove: () => void; onRemove: () => void;
}; };

View File

@ -1,6 +1,7 @@
import React, { useState, useRef } from "react"; import React, { useState, useRef } from "react";
import { Box, IconButton } from "theme-ui"; import { Box, IconButton } from "theme-ui";
import { Stage, Layer, Image } from "react-konva"; import { Stage, Layer, Image } from "react-konva";
import Konva from "konva";
import ReactResizeDetector from "react-resize-detector"; import ReactResizeDetector from "react-resize-detector";
import useMapImage from "../../hooks/useMapImage"; import useMapImage from "../../hooks/useMapImage";
@ -23,8 +24,6 @@ import MapGrid from "./MapGrid";
import MapGridEditor from "./MapGridEditor"; import MapGridEditor from "./MapGridEditor";
import { Map } from "../../types/Map"; import { Map } from "../../types/Map";
import { GridInset } from "../../types/Grid"; import { GridInset } from "../../types/Grid";
import { Stage as StageType } from "konva/types/Stage";
import { Layer as LayerType } from "konva/types/Layer";
type MapSettingsChangeEventHandler = (change: Partial<Map>) => void; type MapSettingsChangeEventHandler = (change: Partial<Map>) => void;
@ -43,8 +42,8 @@ function MapEditor({ map, onSettingsChange }: MapEditorProps) {
const defaultInset = getGridDefaultInset(map.grid, map.width, map.height); const defaultInset = getGridDefaultInset(map.grid, map.width, map.height);
const stageTranslateRef = useRef({ x: 0, y: 0 }); const stageTranslateRef = useRef({ x: 0, y: 0 });
const mapStageRef = useRef<StageType>(null); const mapStageRef = useRef<Konva.Stage>(null);
const mapLayerRef = useRef<LayerType>(null); const mapLayerRef = useRef<Konva.Layer>(null);
const [preventMapInteraction, setPreventMapInteraction] = useState(false); const [preventMapInteraction, setPreventMapInteraction] = useState(false);
function handleResize(width?: number, height?: number): void { function handleResize(width?: number, height?: number): void {

View File

@ -1,6 +1,6 @@
import { useRef } from "react"; import { useRef } from "react";
import { Group, Circle, Rect } from "react-konva"; import { Group, Circle, Rect } from "react-konva";
import { KonvaEventObject, Node } from "konva/types/Node"; import Konva from "konva";
import { import {
useDebouncedStageScale, useDebouncedStageScale,
@ -49,19 +49,23 @@ function MapGridEditor({ map, onGridChange }: MapGridEditorProps) {
const handlePreviousPositionRef = useRef<Vector2>(); const handlePreviousPositionRef = useRef<Vector2>();
function handleScaleCircleDragStart(event: KonvaEventObject<MouseEvent>) { function handleScaleCircleDragStart(
event: Konva.KonvaEventObject<MouseEvent>
) {
const handle = event.target; const handle = event.target;
const position = getHandleNormalizedPosition(handle); const position = getHandleNormalizedPosition(handle);
handlePreviousPositionRef.current = position; handlePreviousPositionRef.current = position;
} }
function handleScaleCircleDragMove(event: KonvaEventObject<MouseEvent>) { function handleScaleCircleDragMove(
event: Konva.KonvaEventObject<MouseEvent>
) {
const handle = event.target; const handle = event.target;
onGridChange(getHandleInset(handle)); onGridChange(getHandleInset(handle));
handlePreviousPositionRef.current = getHandleNormalizedPosition(handle); handlePreviousPositionRef.current = getHandleNormalizedPosition(handle);
} }
function handleScaleCircleDragEnd(event: KonvaEventObject<MouseEvent>) { function handleScaleCircleDragEnd(event: Konva.KonvaEventObject<MouseEvent>) {
onGridChange(getHandleInset(event.target)); onGridChange(getHandleInset(event.target));
setPreventMapInteraction(false); setPreventMapInteraction(false);
} }
@ -74,7 +78,7 @@ function MapGridEditor({ map, onGridChange }: MapGridEditorProps) {
setPreventMapInteraction(false); setPreventMapInteraction(false);
} }
function getHandleInset(handle: Node): GridInset { function getHandleInset(handle: Konva.Node): GridInset {
const name = handle.name(); const name = handle.name();
// Find distance and direction of dragging // Find distance and direction of dragging
@ -202,7 +206,7 @@ function MapGridEditor({ map, onGridChange }: MapGridEditorProps) {
useKeyboard(handleKeyDown); useKeyboard(handleKeyDown);
function getHandleNormalizedPosition(handle: Node) { function getHandleNormalizedPosition(handle: Konva.Node) {
return Vector2.divide({ x: handle.x(), y: handle.y() }, mapSize); return Vector2.divide({ x: handle.x(), y: handle.y() }, mapSize);
} }

View File

@ -2,6 +2,7 @@ import React, { useRef, useEffect, useState } from "react";
import { Box } from "theme-ui"; import { Box } from "theme-ui";
import ReactResizeDetector from "react-resize-detector"; import ReactResizeDetector from "react-resize-detector";
import { Stage, Layer, Image } from "react-konva"; import { Stage, Layer, Image } from "react-konva";
import Konva from "konva";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import useMapImage from "../../hooks/useMapImage"; import useMapImage from "../../hooks/useMapImage";
@ -18,8 +19,6 @@ import { GridProvider } from "../../contexts/GridContext";
import { useKeyboard } from "../../contexts/KeyboardContext"; import { useKeyboard } from "../../contexts/KeyboardContext";
import shortcuts from "../../shortcuts"; import shortcuts from "../../shortcuts";
import { Layer as LayerType } from "konva/types/Layer";
import { Image as ImageType } from "konva/types/shapes/Image";
import { Map, MapToolId } from "../../types/Map"; import { Map, MapToolId } from "../../types/Map";
import { MapState } from "../../types/MapState"; import { MapState } from "../../types/MapState";
@ -63,8 +62,8 @@ function MapInteraction({
// Avoid state udpates when panning the map by using a ref and updating the konva element directly // Avoid state udpates when panning the map by using a ref and updating the konva element directly
const stageTranslateRef = useRef({ x: 0, y: 0 }); const stageTranslateRef = useRef({ x: 0, y: 0 });
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
const mapLayerRef = useRef<LayerType>(null); const mapLayerRef = useRef<Konva.Layer>(null);
const mapImageRef = useRef<ImageType>(null); const mapImageRef = useRef<Konva.Image>(null);
function handleResize(width?: number, height?: number) { function handleResize(width?: number, height?: number) {
if (width && height && width > 0 && height > 0) { if (width && height && width > 0 && height > 0) {
@ -223,7 +222,7 @@ function MapInteraction({
> >
<Layer ref={mapLayerRef}> <Layer ref={mapLayerRef}>
<Image <Image
image={mapLoaded && mapImage} image={(mapLoaded && mapImage) || undefined}
width={mapWidth} width={mapWidth}
height={mapHeight} height={mapHeight}
id="mapImage" id="mapImage"

View File

@ -1,6 +1,7 @@
import React, { useState, useRef } from "react"; import { useState, useRef } from "react";
import { Image as KonvaImage, Group } from "react-konva"; import { Image as KonvaImage, Group } from "react-konva";
import { useSpring, animated } from "@react-spring/konva"; import { useSpring, animated } from "@react-spring/konva";
import Konva from "konva";
import useImage from "use-image"; import useImage from "use-image";
import usePrevious from "../../hooks/usePrevious"; import usePrevious from "../../hooks/usePrevious";
@ -23,6 +24,23 @@ import { Intersection, getScaledOutline } from "../../helpers/token";
import Vector2 from "../../helpers/Vector2"; import Vector2 from "../../helpers/Vector2";
import { tokenSources } from "../../tokens"; import { tokenSources } from "../../tokens";
import { TokenState } from "../../types/TokenState";
import { Map } from "../../types/Map";
import {
TokenMenuOpenChangeEventHandler,
TokenStateChangeEventHandler,
} from "../../types/Events";
type MapTokenStateProps = {
tokenState: TokenState;
onTokenStateChange: TokenStateChangeEventHandler;
onTokenMenuOpen: TokenMenuOpenChangeEventHandler;
onTokenDragStart: (event: Konva.KonvaEventObject<DragEvent>) => void;
onTokenDragEnd: (event: Konva.KonvaEventObject<DragEvent>) => void;
draggable: boolean;
fadeOnHover: boolean;
map: Map;
};
function MapToken({ function MapToken({
tokenState, tokenState,
@ -33,7 +51,7 @@ function MapToken({
draggable, draggable,
fadeOnHover, fadeOnHover,
map, map,
}) { }: MapTokenStateProps) {
const userId = useUserId(); const userId = useUserId();
const mapWidth = useMapWidth(); const mapWidth = useMapWidth();
@ -43,16 +61,16 @@ function MapToken({
const gridCellPixelSize = useGridCellPixelSize(); const gridCellPixelSize = useGridCellPixelSize();
const tokenURL = useDataURL(tokenState, tokenSources); const tokenURL = useDataURL(tokenState, tokenSources);
const [tokenImage] = useImage(tokenURL); const [tokenImage] = useImage(tokenURL || "");
const tokenAspectRatio = tokenState.width / tokenState.height; const tokenAspectRatio = tokenState.width / tokenState.height;
const snapPositionToGrid = useGridSnapping(); const snapPositionToGrid = useGridSnapping();
const intersectingTokensRef = useRef([]); const intersectingTokensRef = useRef<Konva.Node[]>([]);
const previousDragPositionRef = useRef({ x: 0, y: 0 }); const previousDragPositionRef = useRef({ x: 0, y: 0 });
function handleDragStart(event) { function handleDragStart(event: Konva.KonvaEventObject<DragEvent>) {
const tokenGroup = event.target; const tokenGroup = event.target;
if (tokenState.category === "vehicle") { if (tokenState.category === "vehicle") {
@ -65,7 +83,7 @@ function MapToken({
); );
// Find all other tokens on the map // Find all other tokens on the map
const layer = tokenGroup.getLayer(); const layer = tokenGroup.getLayer() as Konva.Layer;
const tokens = layer.find(".character"); const tokens = layer.find(".character");
for (let other of tokens) { for (let other of tokens) {
if (other === tokenGroup) { if (other === tokenGroup) {
@ -80,7 +98,7 @@ function MapToken({
onTokenDragStart(event); onTokenDragStart(event);
} }
function handleDragMove(event) { function handleDragMove(event: Konva.KonvaEventObject<DragEvent>) {
const tokenGroup = event.target; const tokenGroup = event.target;
// Snap to corners of grid // Snap to corners of grid
if (map.snapToGrid) { if (map.snapToGrid) {
@ -98,10 +116,10 @@ function MapToken({
} }
} }
function handleDragEnd(event) { function handleDragEnd(event: Konva.KonvaEventObject<DragEvent>) {
const tokenGroup = event.target; const tokenGroup = event.target;
const mountChanges = {}; const mountChanges: Record<string, Partial<TokenState>> = {};
if (tokenState.category === "vehicle") { if (tokenState.category === "vehicle") {
for (let other of intersectingTokensRef.current) { for (let other of intersectingTokensRef.current) {
mountChanges[other.id()] = { mountChanges[other.id()] = {
@ -127,7 +145,7 @@ function MapToken({
onTokenDragEnd(event); onTokenDragEnd(event);
} }
function handleClick(event) { function handleClick(event: Konva.KonvaEventObject<MouseEvent>) {
if (draggable) { if (draggable) {
const tokenImage = event.target; const tokenImage = event.target;
onTokenMenuOpen(tokenState.id, tokenImage); onTokenMenuOpen(tokenState.id, tokenImage);
@ -136,8 +154,8 @@ function MapToken({
const [tokenOpacity, setTokenOpacity] = useState(1); const [tokenOpacity, setTokenOpacity] = useState(1);
// Store token pointer down time to check for a click when token is locked // Store token pointer down time to check for a click when token is locked
const tokenPointerDownTimeRef = useRef(); const tokenPointerDownTimeRef = useRef<number>(0);
function handlePointerDown(event) { function handlePointerDown(event: Konva.KonvaEventObject<PointerEvent>) {
if (draggable) { if (draggable) {
setPreventMapInteraction(true); setPreventMapInteraction(true);
} }
@ -146,7 +164,7 @@ function MapToken({
} }
} }
function handlePointerUp(event) { function handlePointerUp(event: Konva.KonvaEventObject<PointerEvent>) {
if (draggable) { if (draggable) {
setPreventMapInteraction(false); setPreventMapInteraction(false);
} }

View File

@ -293,7 +293,7 @@ export function useAssetURL(
type FileData = { type FileData = {
file: string; file: string;
type: "file"; type: "file";
thumbnail: string; thumbnail?: string;
quality?: string; quality?: string;
resolutions?: Record<string, string>; resolutions?: Record<string, string>;
}; };

View File

@ -1,7 +1,7 @@
import React, { useContext } from "react"; import React, { useContext } from "react";
import { Stage } from "konva/types/Stage"; import Konva from "konva";
export type MapStage = React.MutableRefObject<Stage | null>; export type MapStage = React.MutableRefObject<Konva.Stage | null>;
const MapStageContext = React.createContext<MapStage | undefined>(undefined); const MapStageContext = React.createContext<MapStage | undefined>(undefined);
export const MapStageProvider = MapStageContext.Provider; export const MapStageProvider = MapStageContext.Provider;

View File

@ -1,14 +1,13 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import Konva from "konva"; import Konva from "konva";
import { Line, Group, Path, Circle } from "react-konva"; import { Line, Group, Path, Circle } from "react-konva";
import { LineConfig } from "konva/types/shapes/Line";
import Color from "color"; import Color from "color";
import Vector2 from "./Vector2"; import Vector2 from "./Vector2";
type HoleyLineProps = { type HoleyLineProps = {
holes: number[][]; holes: number[][];
} & LineConfig; } & Konva.LineConfig;
// Holes should be wound in the opposite direction as the containing points array // Holes should be wound in the opposite direction as the containing points array
export function HoleyLine({ holes, ...props }: HoleyLineProps) { export function HoleyLine({ holes, ...props }: HoleyLineProps) {
@ -115,7 +114,7 @@ export function HoleyLine({ holes, ...props }: HoleyLineProps) {
} }
} }
return <Line {...props} sceneFunc={sceneFunc} />; return <Line {...props} sceneFunc={sceneFunc as any} />;
} }
type TickProps = { type TickProps = {
@ -180,11 +179,11 @@ export function Trail({
segments, segments,
color, color,
}: TrailProps) { }: TrailProps) {
const trailRef: React.MutableRefObject<Konva.Line | undefined> = useRef(); const trailRef = useRef<Konva.Line>(null);
const pointsRef: React.MutableRefObject<TrailPoint[]> = useRef([]); const pointsRef = useRef<TrailPoint[]>([]);
const prevPositionRef = useRef(position); const prevPositionRef = useRef(position);
const positionRef = useRef(position); const positionRef = useRef(position);
const circleRef: React.MutableRefObject<Konva.Circle | undefined> = useRef(); const circleRef = useRef<Konva.Circle>(null);
// Color of the end of the trail // Color of the end of the trail
const transparentColorRef = useRef( const transparentColorRef = useRef(
Color(color).lighten(0.5).alpha(0).string() Color(color).lighten(0.5).alpha(0).string()
@ -250,7 +249,7 @@ export function Trail({
}, []); }, []);
// Custom scene function for drawing a trail from a line // Custom scene function for drawing a trail from a line
function sceneFunc(context: CanvasRenderingContext2D) { function sceneFunc(context: Konva.Context) {
// Resample points to ensure a smooth trail // Resample points to ensure a smooth trail
const resampledPoints = Vector2.resample(pointsRef.current, segments); const resampledPoints = Vector2.resample(pointsRef.current, segments);
if (resampledPoints.length === 0) { if (resampledPoints.length === 0) {
@ -302,6 +301,7 @@ export function Trail({
); );
gradient.addColorStop(0, color); gradient.addColorStop(0, color);
gradient.addColorStop(1, transparentColorRef.current); gradient.addColorStop(1, transparentColorRef.current);
// @ts-ignore
context.fillStyle = gradient; context.fillStyle = gradient;
context.fill(); context.fill();
} }

View File

@ -1,6 +1,6 @@
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import Case from "case"; import Case from "case";
import { Stage } from "konva/types/Stage"; import Konva from "konva";
import blobToBuffer from "./blobToBuffer"; import blobToBuffer from "./blobToBuffer";
import { createThumbnail, getImageOutline } from "./image"; import { createThumbnail, getImageOutline } from "./image";
@ -143,7 +143,7 @@ export async function createTokenFromFile(
} }
export function clientPositionToMapPosition( export function clientPositionToMapPosition(
mapStage: Stage, mapStage: Konva.Stage,
clientPosition: Vector2, clientPosition: Vector2,
checkMapBounds = true checkMapBounds = true
): Vector2 | undefined { ): Vector2 | undefined {

View File

@ -1,4 +1,4 @@
import { Layer } from "konva/types/Layer"; import Konva from "konva";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { MapStage } from "../contexts/MapStageContext"; import { MapStage } from "../contexts/MapStageContext";
@ -17,7 +17,7 @@ function useImageCenter(
stageHeight: number, stageHeight: number,
stageTranslateRef: React.MutableRefObject<Vector2>, stageTranslateRef: React.MutableRefObject<Vector2>,
setStageScale: React.Dispatch<React.SetStateAction<number>>, setStageScale: React.Dispatch<React.SetStateAction<number>>,
imageLayerRef: React.RefObject<Layer>, imageLayerRef: React.RefObject<Konva.Layer>,
containerRef: React.RefObject<HTMLDivElement>, containerRef: React.RefObject<HTMLDivElement>,
responsive = false responsive = false
) { ) {

View File

@ -7,7 +7,9 @@ import { mapSources as defaultMapSources } from "../maps";
import { Map } from "../types/Map"; import { Map } from "../types/Map";
function useMapImage(map: Map) { function useMapImage(
map: Map
): [HTMLImageElement | undefined, "loaded" | "loading" | "failed"] {
const mapURL = useDataURL(map, defaultMapSources); const mapURL = useDataURL(map, defaultMapSources);
const [mapImage, mapImageStatus] = useImage(mapURL || ""); const [mapImage, mapImageStatus] = useImage(mapURL || "");

View File

@ -2,7 +2,7 @@ import { useRef, useEffect, useState } from "react";
import { useGesture } from "react-use-gesture"; import { useGesture } from "react-use-gesture";
import { Handlers } from "react-use-gesture/dist/types"; import { Handlers } from "react-use-gesture/dist/types";
import normalizeWheel from "normalize-wheel"; import normalizeWheel from "normalize-wheel";
import { Layer } from "konva/types/Layer"; import Konva from "konva";
import { useKeyboard, useBlur } from "../contexts/KeyboardContext"; import { useKeyboard, useBlur } from "../contexts/KeyboardContext";
import { MapStage } from "../contexts/MapStageContext"; import { MapStage } from "../contexts/MapStageContext";
@ -22,7 +22,7 @@ function useStageInteraction(
stageScale: number, stageScale: number,
onStageScaleChange: StageScaleChangeEventHandler, onStageScaleChange: StageScaleChangeEventHandler,
stageTranslateRef: React.MutableRefObject<Vector2>, stageTranslateRef: React.MutableRefObject<Vector2>,
layerRef: React.RefObject<Layer>, layerRef: React.RefObject<Konva.Layer>,
maxZoom = 10, maxZoom = 10,
tool = "move", tool = "move",
preventInteraction = false, preventInteraction = false,

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { Flex, Box, Text } from "theme-ui"; import { Flex, Box, Text } from "theme-ui";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Konva from "konva";
import Banner from "../components/banner/Banner"; import Banner from "../components/banner/Banner";
import ReconnectBanner from "../components/banner/ReconnectBanner"; import ReconnectBanner from "../components/banner/ReconnectBanner";
@ -28,7 +29,6 @@ import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens";
import NetworkedParty from "../network/NetworkedParty"; import NetworkedParty from "../network/NetworkedParty";
import Session from "../network/Session"; import Session from "../network/Session";
import { Stage } from "konva/types/Stage";
function Game() { function Game() {
const { id: gameId }: { id: string } = useParams(); const { id: gameId }: { id: string } = useParams();
@ -108,7 +108,7 @@ function Game() {
// A ref to the Konva stage // A ref to the Konva stage
// the ref will be assigned in the MapInteraction component // the ref will be assigned in the MapInteraction component
const mapStageRef = useRef<Stage | null>(null); const mapStageRef = useRef<Konva.Stage | null>(null);
return ( return (
<AssetsProvider> <AssetsProvider>

View File

@ -1,3 +1,4 @@
import Konva from "konva";
import { DefaultDice } from "./Dice"; import { DefaultDice } from "./Dice";
import { Map } from "./Map"; import { Map } from "./Map";
import { MapState } from "./MapState"; import { MapState } from "./MapState";
@ -12,3 +13,11 @@ export type DiceSelectEventHandler = (dice: DefaultDice) => void;
export type RequestCloseEventHandler = () => void; export type RequestCloseEventHandler = () => void;
export type MapTokensStateCreateHandler = (states: TokenState[]) => void; export type MapTokensStateCreateHandler = (states: TokenState[]) => void;
export type TokenStateChangeEventHandler = (
change: Partial<Record<string, Partial<TokenState>>>
) => void;
export type TokenMenuOpenChangeEventHandler = (
tokenStateId: string,
tokenImage: Konva.Node
) => void;