From e6fd10a904fb079d82d4adfe2a80628b9dd4d262 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 12 Mar 2021 11:02:58 +1100 Subject: [PATCH] Optimise grid and interaction context --- src/components/Grid.js | 13 +- src/components/map/MapDrawing.js | 27 ++-- src/components/map/MapFog.js | 42 +++-- src/components/map/MapGridEditor.js | 17 ++- src/components/map/MapMeasure.js | 39 +++-- src/components/map/MapNotes.js | 4 +- src/components/map/MapPointer.js | 14 +- src/components/map/MapTest.js | 5 + src/components/map/MapToken.js | 23 +-- src/components/note/Note.js | 16 +- src/components/token/TokenDragOverlay.js | 9 +- src/contexts/GridContext.js | 186 +++++++++++++++++------ src/contexts/MapInteractionContext.js | 129 ++++++++++++++-- src/hooks/useGridSnapping.js | 17 ++- 14 files changed, 406 insertions(+), 135 deletions(-) create mode 100644 src/components/map/MapTest.js diff --git a/src/components/Grid.js b/src/components/Grid.js index c1a1230..8896cbb 100644 --- a/src/components/Grid.js +++ b/src/components/Grid.js @@ -4,7 +4,12 @@ import useImage from "use-image"; import Vector2 from "../helpers/Vector2"; -import { useGrid } from "../contexts/GridContext"; +import { + useGrid, + useGridPixelSize, + useGridOffset, + useGridCellPixelSize, +} from "../contexts/GridContext"; import squarePatternDark from "../images/SquarePatternDark.png"; import squarePatternLight from "../images/SquarePatternLight.png"; @@ -12,7 +17,11 @@ import hexPatternDark from "../images/HexPatternDark.png"; import hexPatternLight from "../images/HexPatternLight.png"; function Grid({ stroke }) { - const { grid, gridPixelSize, gridOffset, gridCellPixelSize } = useGrid(); + const grid = useGrid(); + const gridPixelSize = useGridPixelSize(); + const gridOffset = useGridOffset(); + const gridCellPixelSize = useGridCellPixelSize(); + let imageSource; if (grid.type === "square") { if (stroke === "black") { diff --git a/src/components/map/MapDrawing.js b/src/components/map/MapDrawing.js index 868ac7b..54e1234 100644 --- a/src/components/map/MapDrawing.js +++ b/src/components/map/MapDrawing.js @@ -2,9 +2,17 @@ import React, { useState, useEffect } from "react"; import shortid from "shortid"; import { Group, Line, Rect, Circle } from "react-konva"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; +import { + useDebouncedStageScale, + useMapWidth, + useMapHeight, + useInteractionEmitter, +} from "../../contexts/MapInteractionContext"; import { useMapStage } from "../../contexts/MapStageContext"; -import { useGrid } from "../../contexts/GridContext"; +import { + useGridCellNormalizedSize, + useGridStrokeWidth, +} from "../../contexts/GridContext"; import Vector2 from "../../helpers/Vector2"; import { @@ -25,13 +33,14 @@ function MapDrawing({ active, toolSettings, }) { - const { - stageScale, - mapWidth, - mapHeight, - interactionEmitter, - } = useMapInteraction(); - const { gridCellNormalizedSize, gridStrokeWidth } = useGrid(); + const stageScale = useDebouncedStageScale(); + const mapWidth = useMapWidth(); + const mapHeight = useMapHeight(); + const interactionEmitter = useInteractionEmitter(); + + const gridCellNormalizedSize = useGridCellNormalizedSize(); + const gridStrokeWidth = useGridStrokeWidth(); + const mapStageRef = useMapStage(); const [drawingShape, setDrawingShape] = useState(null); const [isBrushDown, setIsBrushDown] = useState(false); diff --git a/src/components/map/MapFog.js b/src/components/map/MapFog.js index 41230fd..dbd3ee1 100644 --- a/src/components/map/MapFog.js +++ b/src/components/map/MapFog.js @@ -5,9 +5,21 @@ import useImage from "use-image"; import diagonalPattern from "../../images/DiagonalPattern.png"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; +import { + useDebouncedStageScale, + useMapWidth, + useMapHeight, + useInteractionEmitter, +} from "../../contexts/MapInteractionContext"; import { useMapStage } from "../../contexts/MapStageContext"; -import { useGrid } from "../../contexts/GridContext"; +import { + useGrid, + useGridCellPixelSize, + useGridCellNormalizedSize, + useGridStrokeWidth, + useGridCellPixelOffset, + useGridOffset, +} from "../../contexts/GridContext"; import { useKeyboard } from "../../contexts/KeyboardContext"; import Vector2 from "../../helpers/Vector2"; @@ -41,20 +53,18 @@ function MapFog({ toolSettings, editable, }) { - const { - stageScale, - mapWidth, - mapHeight, - interactionEmitter, - } = useMapInteraction(); - const { - grid, - gridCellNormalizedSize, - gridCellPixelSize, - gridStrokeWidth, - gridCellPixelOffset, - gridOffset, - } = useGrid(); + const stageScale = useDebouncedStageScale(); + const mapWidth = useMapWidth(); + const mapHeight = useMapHeight(); + const interactionEmitter = useInteractionEmitter(); + + const grid = useGrid(); + const gridCellNormalizedSize = useGridCellNormalizedSize(); + const gridCellPixelSize = useGridCellPixelSize(); + const gridStrokeWidth = useGridStrokeWidth(); + const gridCellPixelOffset = useGridCellPixelOffset(); + const gridOffset = useGridOffset(); + const [gridSnappingSensitivity] = useSetting("map.gridSnappingSensitivity"); const mapStageRef = useMapStage(); diff --git a/src/components/map/MapGridEditor.js b/src/components/map/MapGridEditor.js index ccc316d..9957b73 100644 --- a/src/components/map/MapGridEditor.js +++ b/src/components/map/MapGridEditor.js @@ -1,18 +1,21 @@ import React, { useRef } from "react"; import { Group, Circle, Rect } from "react-konva"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; +import { + useDebouncedStageScale, + useMapWidth, + useMapHeight, + useSetPreventMapInteraction, +} from "../../contexts/MapInteractionContext"; import { useKeyboard } from "../../contexts/KeyboardContext"; import Vector2 from "../../helpers/Vector2"; function MapGridEditor({ map, onGridChange }) { - const { - mapWidth, - mapHeight, - stageScale, - setPreventMapInteraction, - } = useMapInteraction(); + const stageScale = useDebouncedStageScale(); + const mapWidth = useMapWidth(); + const mapHeight = useMapHeight(); + const setPreventMapInteraction = useSetPreventMapInteraction(); const mapSize = { x: mapWidth, y: mapHeight }; diff --git a/src/components/map/MapMeasure.js b/src/components/map/MapMeasure.js index 120bfef..f9318c9 100644 --- a/src/components/map/MapMeasure.js +++ b/src/components/map/MapMeasure.js @@ -1,9 +1,20 @@ import React, { useState, useEffect } from "react"; import { Group, Line, Text, Label, Tag } from "react-konva"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; +import { + useDebouncedStageScale, + useMapWidth, + useMapHeight, + useInteractionEmitter, +} from "../../contexts/MapInteractionContext"; import { useMapStage } from "../../contexts/MapStageContext"; -import { useGrid } from "../../contexts/GridContext"; +import { + useGrid, + useGridCellPixelSize, + useGridCellNormalizedSize, + useGridStrokeWidth, + useGridOffset, +} from "../../contexts/GridContext"; import { getDefaultShapeData, @@ -16,19 +27,17 @@ import { parseGridScale, gridDistance } from "../../helpers/grid"; import useGridSnapping from "../../hooks/useGridSnapping"; function MapMeasure({ map, active }) { - const { - stageScale, - mapWidth, - mapHeight, - interactionEmitter, - } = useMapInteraction(); - const { - grid, - gridCellNormalizedSize, - gridStrokeWidth, - gridCellPixelSize, - gridOffset, - } = useGrid(); + const stageScale = useDebouncedStageScale(); + const mapWidth = useMapWidth(); + const mapHeight = useMapHeight(); + const interactionEmitter = useInteractionEmitter(); + + const grid = useGrid(); + const gridCellNormalizedSize = useGridCellNormalizedSize(); + const gridCellPixelSize = useGridCellPixelSize(); + const gridStrokeWidth = useGridStrokeWidth(); + const gridOffset = useGridOffset(); + const mapStageRef = useMapStage(); const [drawingShapeData, setDrawingShapeData] = useState(null); const [isBrushDown, setIsBrushDown] = useState(false); diff --git a/src/components/map/MapNotes.js b/src/components/map/MapNotes.js index aff5ec1..c2706c8 100644 --- a/src/components/map/MapNotes.js +++ b/src/components/map/MapNotes.js @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from "react"; import shortid from "shortid"; import { Group } from "react-konva"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; +import { useInteractionEmitter } from "../../contexts/MapInteractionContext"; import { useMapStage } from "../../contexts/MapStageContext"; import { useAuth } from "../../contexts/AuthContext"; @@ -27,7 +27,7 @@ function MapNotes({ onNoteDragEnd, fadeOnHover, }) { - const { interactionEmitter } = useMapInteraction(); + const interactionEmitter = useInteractionEmitter(); const { userId } = useAuth(); const mapStageRef = useMapStage(); const [isBrushDown, setIsBrushDown] = useState(false); diff --git a/src/components/map/MapPointer.js b/src/components/map/MapPointer.js index 952ff11..e78ac33 100644 --- a/src/components/map/MapPointer.js +++ b/src/components/map/MapPointer.js @@ -1,9 +1,13 @@ import React, { useEffect } from "react"; import { Group } from "react-konva"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; +import { + useMapWidth, + useMapHeight, + useInteractionEmitter, +} from "../../contexts/MapInteractionContext"; import { useMapStage } from "../../contexts/MapStageContext"; -import { useGrid } from "../../contexts/GridContext"; +import { useGridStrokeWidth } from "../../contexts/GridContext"; import { getRelativePointerPositionNormalized, @@ -22,8 +26,10 @@ function MapPointer({ visible, color, }) { - const { mapWidth, mapHeight, interactionEmitter } = useMapInteraction(); - const { gridStrokeWidth } = useGrid(); + const mapWidth = useMapWidth(); + const mapHeight = useMapHeight(); + const interactionEmitter = useInteractionEmitter(); + const gridStrokeWidth = useGridStrokeWidth(); const mapStageRef = useMapStage(); useEffect(() => { diff --git a/src/components/map/MapTest.js b/src/components/map/MapTest.js new file mode 100644 index 0000000..25a914b --- /dev/null +++ b/src/components/map/MapTest.js @@ -0,0 +1,5 @@ +import React from "react"; + +function MapTest() {} + +export default MapTest; diff --git a/src/components/map/MapToken.js b/src/components/map/MapToken.js index e340ed1..a607fb5 100644 --- a/src/components/map/MapToken.js +++ b/src/components/map/MapToken.js @@ -10,8 +10,13 @@ import usePrevious from "../../hooks/usePrevious"; import useGridSnapping from "../../hooks/useGridSnapping"; import { useAuth } from "../../contexts/AuthContext"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; -import { useGrid } from "../../contexts/GridContext"; +import { + useSetPreventMapInteraction, + useMapWidth, + useMapHeight, + useDebouncedStageScale, +} from "../../contexts/MapInteractionContext"; +import { useGridCellPixelSize } from "../../contexts/GridContext"; import TokenStatus from "../token/TokenStatus"; import TokenLabel from "../token/TokenLabel"; @@ -31,13 +36,13 @@ function MapToken({ map, }) { const { userId } = useAuth(); - const { - setPreventMapInteraction, - mapWidth, - mapHeight, - stageScale, - } = useMapInteraction(); - const { gridCellPixelSize } = useGrid(); + + const stageScale = useDebouncedStageScale(); + const mapWidth = useMapWidth(); + const mapHeight = useMapHeight(); + const setPreventMapInteraction = useSetPreventMapInteraction(); + + const gridCellPixelSize = useGridCellPixelSize(); const tokenSource = useDataSource(token, tokenSources, unknownSource); const [tokenSourceImage, tokenSourceStatus] = useImage(tokenSource); diff --git a/src/components/note/Note.js b/src/components/note/Note.js index 1aacc4d..072e668 100644 --- a/src/components/note/Note.js +++ b/src/components/note/Note.js @@ -3,8 +3,12 @@ import { Rect, Text } from "react-konva"; import { useSpring, animated } from "react-spring/konva"; import { useAuth } from "../../contexts/AuthContext"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; -import { useGrid } from "../../contexts/GridContext"; +import { + useSetPreventMapInteraction, + useMapWidth, + useMapHeight, +} from "../../contexts/MapInteractionContext"; +import { useGridCellPixelSize } from "../../contexts/GridContext"; import colors from "../../helpers/colors"; @@ -24,8 +28,12 @@ function Note({ fadeOnHover, }) { const { userId } = useAuth(); - const { mapWidth, mapHeight, setPreventMapInteraction } = useMapInteraction(); - const { gridCellPixelSize } = useGrid(); + + const mapWidth = useMapWidth(); + const mapHeight = useMapHeight(); + const setPreventMapInteraction = useSetPreventMapInteraction(); + + const gridCellPixelSize = useGridCellPixelSize(); const minCellSize = Math.min( gridCellPixelSize.width, diff --git a/src/components/token/TokenDragOverlay.js b/src/components/token/TokenDragOverlay.js index 467967f..721d05d 100644 --- a/src/components/token/TokenDragOverlay.js +++ b/src/components/token/TokenDragOverlay.js @@ -1,7 +1,10 @@ import React from "react"; import { useAuth } from "../../contexts/AuthContext"; -import { useMapInteraction } from "../../contexts/MapInteractionContext"; +import { + useMapWidth, + useMapHeight, +} from "../../contexts/MapInteractionContext"; import DragOverlay from "../DragOverlay"; @@ -15,7 +18,9 @@ function TokenDragOverlay({ mapState, }) { const { userId } = useAuth(); - const { mapWidth, mapHeight } = useMapInteraction(); + + const mapWidth = useMapWidth(); + const mapHeight = useMapHeight(); function handleTokenRemove() { // Handle other tokens when a vehicle gets deleted diff --git a/src/contexts/GridContext.js b/src/contexts/GridContext.js index 6598000..b0ec78e 100644 --- a/src/contexts/GridContext.js +++ b/src/contexts/GridContext.js @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React, { useContext, useState, useEffect } from "react"; import Vector2 from "../helpers/Vector2"; import Size from "../helpers/Size"; @@ -37,55 +37,103 @@ const defaultValue = { gridCellPixelOffset: new Vector2(0, 0), }; -const GridContext = React.createContext(defaultValue); +const GridContext = React.createContext(defaultValue.grid); +const GridPixelSizeContext = React.createContext(defaultValue.gridPixelSize); +const GridCellPixelSizeContext = React.createContext( + defaultValue.gridCellPixelSize +); +const GridCellNormalizedSizeContext = React.createContext( + defaultValue.gridCellNormalizedSize +); +const GridOffsetContext = React.createContext(defaultValue.gridOffset); +const GridStrokeWidthContext = React.createContext( + defaultValue.gridStrokeWidth +); +const GridCellPixelOffsetContext = React.createContext( + defaultValue.gridCellPixelOffset +); const defaultStrokeWidth = 1 / 10; -export function GridProvider({ grid, width, height, children }) { +export function GridProvider({ grid: inputGrid, width, height, children }) { + let grid = inputGrid; + if (!grid?.size.x || !grid?.size.y) { - return ( - - {children} - + grid = defaultValue.grid; + } + + const [gridPixelSize, setGridPixelSize] = useState( + defaultValue.gridCellPixelSize + ); + const [gridCellPixelSize, setGridCellPixelSize] = useState( + defaultValue.gridCellPixelSize + ); + const [gridCellNormalizedSize, setGridCellNormalizedSize] = useState( + defaultValue.gridCellNormalizedSize + ); + const [gridOffset, setGridOffset] = useState(defaultValue.gridOffset); + const [gridStrokeWidth, setGridStrokeWidth] = useState( + defaultValue.gridStrokeWidth + ); + const [gridCellPixelOffset, setGridCellPixelOffset] = useState( + defaultValue.gridCellPixelOffset + ); + + useEffect(() => { + const _gridPixelSize = getGridPixelSize(grid, width, height); + const _gridCellPixelSize = getCellPixelSize( + grid, + _gridPixelSize.width, + _gridPixelSize.height ); - } + const _gridCellNormalizedSize = new Size( + _gridCellPixelSize.width / width, + _gridCellPixelSize.height / height + ); + const _gridOffset = Vector2.multiply(grid.inset.topLeft, { + x: width, + y: height, + }); + const _gridStrokeWidth = + (_gridCellPixelSize.width < _gridCellPixelSize.height + ? _gridCellPixelSize.width + : _gridCellPixelSize.height) * defaultStrokeWidth; - const gridPixelSize = getGridPixelSize(grid, width, height); - const gridCellPixelSize = getCellPixelSize( - grid, - gridPixelSize.width, - gridPixelSize.height + let _gridCellPixelOffset = { x: 0, y: 0 }; + // Move hex tiles to top left + if (grid.type === "hexVertical" || grid.type === "hexHorizontal") { + _gridCellPixelOffset = Vector2.multiply(_gridCellPixelSize, 0.5); + } + + setGridPixelSize(_gridPixelSize); + setGridCellPixelSize(_gridCellPixelSize); + setGridCellNormalizedSize(_gridCellNormalizedSize); + setGridOffset(_gridOffset); + setGridStrokeWidth(_gridStrokeWidth); + setGridCellPixelOffset(_gridCellPixelOffset); + }, [grid, width, height]); + + return ( + + + + + + + + {children} + + + + + + + ); - const gridCellNormalizedSize = new Size( - gridCellPixelSize.width / width, - gridCellPixelSize.height / height - ); - const gridOffset = Vector2.multiply(grid.inset.topLeft, { - x: width, - y: height, - }); - const gridStrokeWidth = - (gridCellPixelSize.width < gridCellPixelSize.height - ? gridCellPixelSize.width - : gridCellPixelSize.height) * defaultStrokeWidth; - - let gridCellPixelOffset = { x: 0, y: 0 }; - // Move hex tiles to top left - if (grid.type === "hexVertical" || grid.type === "hexHorizontal") { - gridCellPixelOffset = Vector2.multiply(gridCellPixelSize, 0.5); - } - - const value = { - grid, - gridPixelSize, - gridCellPixelSize, - gridCellNormalizedSize, - gridOffset, - gridStrokeWidth, - gridCellPixelOffset, - }; - - return {children}; } export function useGrid() { @@ -96,4 +144,54 @@ export function useGrid() { return context; } -export default GridContext; +export function useGridPixelSize() { + const context = useContext(GridPixelSizeContext); + if (context === undefined) { + throw new Error("useGridPixelSize must be used within a GridProvider"); + } + return context; +} + +export function useGridCellPixelSize() { + const context = useContext(GridCellPixelSizeContext); + if (context === undefined) { + throw new Error("useGridCellPixelSize must be used within a GridProvider"); + } + return context; +} + +export function useGridCellNormalizedSize() { + const context = useContext(GridCellNormalizedSizeContext); + if (context === undefined) { + throw new Error( + "useGridCellNormalizedSize must be used within a GridProvider" + ); + } + return context; +} + +export function useGridOffset() { + const context = useContext(GridOffsetContext); + if (context === undefined) { + throw new Error("useGridOffset must be used within a GridProvider"); + } + return context; +} + +export function useGridStrokeWidth() { + const context = useContext(GridStrokeWidthContext); + if (context === undefined) { + throw new Error("useGridStrokeWidth must be used within a GridProvider"); + } + return context; +} + +export function useGridCellPixelOffset() { + const context = useContext(GridCellPixelOffsetContext); + if (context === undefined) { + throw new Error( + "useGridCellPixelOffset must be used within a GridProvider" + ); + } + return context; +} diff --git a/src/contexts/MapInteractionContext.js b/src/contexts/MapInteractionContext.js index 905b1fe..1a8a434 100644 --- a/src/contexts/MapInteractionContext.js +++ b/src/contexts/MapInteractionContext.js @@ -1,24 +1,125 @@ import React, { useContext } from "react"; +import useDebounce from "../hooks/useDebounce"; -const MapInteractionContext = React.createContext({ - stageScale: 1, - stageWidth: 1, - stageHeight: 1, - setPreventMapInteraction: () => {}, - mapWidth: 1, - mapHeight: 1, - interactionEmitter: null, -}); -export const MapInteractionProvider = MapInteractionContext.Provider; +const StageScaleContext = React.createContext(); +const DebouncedStageScaleContext = React.createContext(); +const StageWidthContext = React.createContext(); +const StageHeightContext = React.createContext(); +const SetPreventMapInteractionContext = React.createContext(); +const MapWidthContext = React.createContext(); +const MapHeightContext = React.createContext(); +const InteractionEmitterContext = React.createContext(); -export function useMapInteraction() { - const context = useContext(MapInteractionContext); +export function MapInteractionProvider({ value, children }) { + const { + stageScale, + stageWidth, + stageHeight, + setPreventMapInteraction, + mapWidth, + mapHeight, + interactionEmitter, + } = value; + const debouncedStageScale = useDebounce(stageScale, 200); + return ( + + + + + + + + + {children} + + + + + + + + + ); +} + +export function useInteractionEmitter() { + const context = useContext(InteractionEmitterContext); if (context === undefined) { throw new Error( - "useMapInteraction must be used within a MapInteractionProvider" + "useInteractionEmitter must be used within a MapInteractionProvider" ); } return context; } -export default MapInteractionContext; +export function useSetPreventMapInteraction() { + const context = useContext(SetPreventMapInteractionContext); + if (context === undefined) { + throw new Error( + "useSetPreventMapInteraction must be used within a MapInteractionProvider" + ); + } + return context; +} + +export function useStageWidth() { + const context = useContext(StageWidthContext); + if (context === undefined) { + throw new Error( + "useStageWidth must be used within a MapInteractionProvider" + ); + } + return context; +} + +export function useStageHeight() { + const context = useContext(StageHeightContext); + if (context === undefined) { + throw new Error( + "useStageHeight must be used within a MapInteractionProvider" + ); + } + return context; +} + +export function useMapWidth() { + const context = useContext(MapWidthContext); + if (context === undefined) { + throw new Error("useMapWidth must be used within a MapInteractionProvider"); + } + return context; +} + +export function useMapHeight() { + const context = useContext(MapHeightContext); + if (context === undefined) { + throw new Error( + "useMapHeight must be used within a MapInteractionProvider" + ); + } + return context; +} + +export function useStageScale() { + const context = useContext(StageScaleContext); + if (context === undefined) { + throw new Error( + "useStageScale must be used within a MapInteractionProvider" + ); + } + return context; +} + +export function useDebouncedStageScale() { + const context = useContext(DebouncedStageScaleContext); + if (context === undefined) { + throw new Error( + "useDebouncedStageScale must be used within a MapInteractionProvider" + ); + } + return context; +} diff --git a/src/hooks/useGridSnapping.js b/src/hooks/useGridSnapping.js index ddaaee9..9c1a85c 100644 --- a/src/hooks/useGridSnapping.js +++ b/src/hooks/useGridSnapping.js @@ -7,7 +7,12 @@ import { import useSetting from "./useSetting"; -import { useGrid } from "../contexts/GridContext"; +import { + useGrid, + useGridOffset, + useGridCellPixelSize, + useGridCellPixelOffset, +} from "../contexts/GridContext"; /** * Returns a function that when called will snap a node to the current grid @@ -19,12 +24,10 @@ function useGridSnapping(snappingSensitivity) { ); snappingSensitivity = snappingSensitivity || defaultSnappingSensitivity; - const { - grid, - gridOffset, - gridCellPixelSize, - gridCellPixelOffset, - } = useGrid(); + const grid = useGrid(); + const gridOffset = useGridOffset(); + const gridCellPixelSize = useGridCellPixelSize(); + const gridCellPixelOffset = useGridCellPixelOffset(); /** * @param {Vector2} node The node to snap