Moved konva context bridging to shared component

This commit is contained in:
Mitchell McCaffrey 2021-03-13 14:23:37 +11:00
parent d1c62a51c0
commit 89fb803398
5 changed files with 287 additions and 170 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useContext } from "react";
import React, { useState, useRef } from "react";
import { Box, IconButton } from "theme-ui";
import { Stage, Layer, Image } from "react-konva";
import ReactResizeDetector from "react-resize-detector";
@ -10,9 +10,9 @@ import useImageCenter from "../../hooks/useImageCenter";
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
import { getGridDefaultInset, getGridMaxZoom } from "../../helpers/grid";
import KonvaBridge from "../../helpers/KonvaBridge";
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
import KeyboardContext from "../../contexts/KeyboardContext";
import { GridProvider } from "../../contexts/GridContext";
import ResetMapIcon from "../../icons/ResetMapIcon";
@ -90,11 +90,9 @@ function MapEditor({ map, onSettingsChange }) {
setPreventMapInteraction,
mapWidth,
mapHeight,
interactionEmitter: null,
};
// Get keyboard context to pass to Konva
const keyboardValue = useContext(KeyboardContext);
const canEditGrid = map.type !== "default";
const gridChanged =
@ -106,77 +104,90 @@ function MapEditor({ map, onSettingsChange }) {
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={mapStageRef}
<MapInteractionProvider value={mapInteraction}>
<GridProvider grid={map?.grid} width={mapWidth} height={mapHeight}>
<Box
sx={{
width: "100%",
height: layout.screenSize === "large" ? "500px" : "300px",
cursor: "move",
touchAction: "none",
outline: "none",
position: "relative",
}}
bg="muted"
ref={containerRef}
>
<Layer ref={mapLayerRef}>
<Image image={mapImageSource} width={mapWidth} height={mapHeight} />
<KeyboardContext.Provider value={keyboardValue}>
<MapInteractionProvider value={mapInteraction}>
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
<KonvaBridge
stageRender={(children) => (
<Stage
width={stageWidth}
height={stageHeight}
scale={{ x: stageScale, y: stageScale }}
ref={mapStageRef}
>
{children}
</Stage>
)}
>
<Layer ref={mapLayerRef}>
<Image
image={mapImageSource}
width={mapWidth}
height={mapHeight}
/>
{showGridControls && canEditGrid && (
<GridProvider
grid={map.grid}
width={mapWidth}
height={mapHeight}
>
<>
<MapGrid map={map} />
<MapGridEditor map={map} onGridChange={handleGridChange} />
</GridProvider>
</>
)}
</MapInteractionProvider>
</KeyboardContext.Provider>
</Layer>
</Stage>
</ReactResizeDetector>
{gridChanged && (
<IconButton
title="Reset Grid"
aria-label="Reset Grid"
onClick={handleMapReset}
bg="overlay"
sx={{ borderRadius: "50%", position: "absolute", bottom: 0, left: 0 }}
m={2}
>
<ResetMapIcon />
</IconButton>
)}
{canEditGrid && (
<IconButton
title={showGridControls ? "Hide Grid Controls" : "Show Grid Controls"}
aria-label={
showGridControls ? "Hide Grid Controls" : "Show Grid Controls"
}
onClick={() => setShowGridControls(!showGridControls)}
bg="overlay"
sx={{
borderRadius: "50%",
position: "absolute",
bottom: 0,
right: 0,
}}
m={2}
p="6px"
>
{showGridControls ? <GridOnIcon /> : <GridOffIcon />}
</IconButton>
)}
</Box>
</Layer>
</KonvaBridge>
</ReactResizeDetector>
{gridChanged && (
<IconButton
title="Reset Grid"
aria-label="Reset Grid"
onClick={handleMapReset}
bg="overlay"
sx={{
borderRadius: "50%",
position: "absolute",
bottom: 0,
left: 0,
}}
m={2}
>
<ResetMapIcon />
</IconButton>
)}
{canEditGrid && (
<IconButton
title={
showGridControls ? "Hide Grid Controls" : "Show Grid Controls"
}
aria-label={
showGridControls ? "Hide Grid Controls" : "Show Grid Controls"
}
onClick={() => setShowGridControls(!showGridControls)}
bg="overlay"
sx={{
borderRadius: "50%",
position: "absolute",
bottom: 0,
right: 0,
}}
m={2}
p="6px"
>
{showGridControls ? <GridOnIcon /> : <GridOffIcon />}
</IconButton>
)}
</Box>
</GridProvider>
</MapInteractionProvider>
);
}

View File

@ -10,21 +10,12 @@ import useStageInteraction from "../../hooks/useStageInteraction";
import useImageCenter from "../../hooks/useImageCenter";
import { getGridMaxZoom } from "../../helpers/grid";
import KonvaBridge from "../../helpers/KonvaBridge";
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
import { MapStageProvider, useMapStage } from "../../contexts/MapStageContext";
import AuthContext, { useAuth } from "../../contexts/AuthContext";
import SettingsContext, { useSettings } from "../../contexts/SettingsContext";
import KeyboardContext from "../../contexts/KeyboardContext";
import TokenDataContext, {
useTokenData,
} from "../../contexts/TokenDataContext";
import { useMapStage } from "../../contexts/MapStageContext";
import { GridProvider } from "../../contexts/GridContext";
import { useKeyboard } from "../../contexts/KeyboardContext";
import {
ImageSourcesStateContext,
ImageSourcesUpdaterContext,
} from "../../contexts/ImageSourceContext";
function MapInteraction({
map,
@ -162,8 +153,6 @@ function MapInteraction({
}
useKeyboard(handleKeyDown, handleKeyUp);
// Get keyboard context to pass to Konva
const keyboardValue = useContext(KeyboardContext);
function getCursorForTool(tool) {
switch (tool) {
@ -171,9 +160,7 @@ function MapInteraction({
return "move";
case "fog":
case "drawing":
return settings.settings[tool].type === "move"
? "pointer"
: "crosshair";
return "crosshair";
case "measure":
case "pointer":
case "note":
@ -183,12 +170,6 @@ function MapInteraction({
}
}
const auth = useAuth();
const settings = useSettings();
const tokenData = useTokenData();
const imageSources = useContext(ImageSourcesStateContext);
const setImageSources = useContext(ImageSourcesUpdaterContext);
const mapInteraction = {
stageScale,
stageWidth,
@ -200,69 +181,49 @@ function MapInteraction({
};
return (
<Box
sx={{
flexGrow: 1,
position: "relative",
cursor: getCursorForTool(selectedToolId),
touchAction: "none",
outline: "none",
}}
ref={containerRef}
className="map"
>
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
<Stage
width={stageWidth}
height={stageHeight}
scale={{ x: stageScale, y: stageScale }}
ref={mapStageRef}
<MapInteractionProvider value={mapInteraction}>
<GridProvider grid={map?.grid} width={mapWidth} height={mapHeight}>
<Box
sx={{
flexGrow: 1,
position: "relative",
cursor: getCursorForTool(selectedToolId),
touchAction: "none",
outline: "none",
}}
ref={containerRef}
className="map"
>
<Layer ref={mapLayerRef}>
<Image
image={mapLoaded && mapImageSource}
width={mapWidth}
height={mapHeight}
id="mapImage"
ref={mapImageRef}
/>
{/* Forward auth context to konva elements */}
<AuthContext.Provider value={auth}>
<SettingsContext.Provider value={settings}>
<KeyboardContext.Provider value={keyboardValue}>
<MapInteractionProvider value={mapInteraction}>
<GridProvider
grid={map?.grid}
width={mapWidth}
height={mapHeight}
>
<MapStageProvider value={mapStageRef}>
<TokenDataContext.Provider value={tokenData}>
<ImageSourcesStateContext.Provider
value={imageSources}
>
<ImageSourcesUpdaterContext.Provider
value={setImageSources}
>
{mapLoaded && children}
</ImageSourcesUpdaterContext.Provider>
</ImageSourcesStateContext.Provider>
</TokenDataContext.Provider>
</MapStageProvider>
</GridProvider>
</MapInteractionProvider>
</KeyboardContext.Provider>
</SettingsContext.Provider>
</AuthContext.Provider>
</Layer>
</Stage>
</ReactResizeDetector>
<MapInteractionProvider value={mapInteraction}>
<GridProvider grid={map?.grid} width={mapWidth} height={mapHeight}>
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
<KonvaBridge
stageRender={(children) => (
<Stage
width={stageWidth}
height={stageHeight}
scale={{ x: stageScale, y: stageScale }}
ref={mapStageRef}
>
{children}
</Stage>
)}
>
<Layer ref={mapLayerRef}>
<Image
image={mapLoaded && mapImageSource}
width={mapWidth}
height={mapHeight}
id="mapImage"
ref={mapImageRef}
/>
{mapLoaded && children}
</Layer>
</KonvaBridge>
</ReactResizeDetector>
{controls}
</GridProvider>
</MapInteractionProvider>
</Box>
</Box>
</GridProvider>
</MapInteractionProvider>
);
}

View File

@ -37,19 +37,21 @@ const defaultValue = {
gridCellPixelOffset: new Vector2(0, 0),
};
const GridContext = React.createContext(defaultValue.grid);
const GridPixelSizeContext = React.createContext(defaultValue.gridPixelSize);
const GridCellPixelSizeContext = React.createContext(
export const GridContext = React.createContext(defaultValue.grid);
export const GridPixelSizeContext = React.createContext(
defaultValue.gridPixelSize
);
export const GridCellPixelSizeContext = React.createContext(
defaultValue.gridCellPixelSize
);
const GridCellNormalizedSizeContext = React.createContext(
export const GridCellNormalizedSizeContext = React.createContext(
defaultValue.gridCellNormalizedSize
);
const GridOffsetContext = React.createContext(defaultValue.gridOffset);
const GridStrokeWidthContext = React.createContext(
export const GridOffsetContext = React.createContext(defaultValue.gridOffset);
export const GridStrokeWidthContext = React.createContext(
defaultValue.gridStrokeWidth
);
const GridCellPixelOffsetContext = React.createContext(
export const GridCellPixelOffsetContext = React.createContext(
defaultValue.gridCellPixelOffset
);

View File

@ -1,14 +1,14 @@
import React, { useContext } from "react";
import useDebounce from "../hooks/useDebounce";
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 const StageScaleContext = React.createContext();
export const DebouncedStageScaleContext = React.createContext();
export const StageWidthContext = React.createContext();
export const StageHeightContext = React.createContext();
export const SetPreventMapInteractionContext = React.createContext();
export const MapWidthContext = React.createContext();
export const MapHeightContext = React.createContext();
export const InteractionEmitterContext = React.createContext();
export function MapInteractionProvider({ value, children }) {
const {
@ -32,7 +32,7 @@ export function MapInteractionProvider({ value, children }) {
<MapHeightContext.Provider value={mapHeight}>
<StageScaleContext.Provider value={stageScale}>
<DebouncedStageScaleContext.Provider
value={debouncedStageScale}
value={debouncedStageScale || 1}
>
{children}
</DebouncedStageScaleContext.Provider>

143
src/helpers/KonvaBridge.js Normal file
View File

@ -0,0 +1,143 @@
import React, { useContext } from "react";
import {
InteractionEmitterContext,
SetPreventMapInteractionContext,
StageWidthContext,
StageHeightContext,
MapWidthContext,
MapHeightContext,
StageScaleContext,
DebouncedStageScaleContext,
useInteractionEmitter,
useSetPreventMapInteraction,
useStageWidth,
useStageHeight,
useMapWidth,
useMapHeight,
useStageScale,
useDebouncedStageScale,
} from "../contexts/MapInteractionContext";
import { MapStageProvider, useMapStage } from "../contexts/MapStageContext";
import AuthContext, { useAuth } from "../contexts/AuthContext";
import SettingsContext, { useSettings } from "../contexts/SettingsContext";
import KeyboardContext from "../contexts/KeyboardContext";
import TokenDataContext, { useTokenData } from "../contexts/TokenDataContext";
import {
ImageSourcesStateContext,
ImageSourcesUpdaterContext,
} from "../contexts/ImageSourceContext";
import {
useGrid,
useGridCellPixelSize,
useGridCellNormalizedSize,
useGridStrokeWidth,
useGridCellPixelOffset,
useGridOffset,
useGridPixelSize,
GridContext,
GridPixelSizeContext,
GridCellPixelSizeContext,
GridCellNormalizedSizeContext,
GridOffsetContext,
GridStrokeWidthContext,
GridCellPixelOffsetContext,
} from "../contexts/GridContext";
/**
* Provide a bridge for konva that forwards our contexts
*/
function KonvaBridge({ stageRender, children }) {
const mapStageRef = useMapStage();
const auth = useAuth();
const settings = useSettings();
const tokenData = useTokenData();
const imageSources = useContext(ImageSourcesStateContext);
const setImageSources = useContext(ImageSourcesUpdaterContext);
const keyboardValue = useContext(KeyboardContext);
const stageScale = useStageScale();
const stageWidth = useStageWidth();
const stageHeight = useStageHeight();
const setPreventMapInteraction = useSetPreventMapInteraction();
const mapWidth = useMapWidth();
const mapHeight = useMapHeight();
const interactionEmitter = useInteractionEmitter();
const debouncedStageScale = useDebouncedStageScale();
const grid = useGrid();
const gridPixelSize = useGridPixelSize();
const gridCellNormalizedSize = useGridCellNormalizedSize();
const gridCellPixelSize = useGridCellPixelSize();
const gridStrokeWidth = useGridStrokeWidth();
const gridCellPixelOffset = useGridCellPixelOffset();
const gridOffset = useGridOffset();
return stageRender(
<AuthContext.Provider value={auth}>
<SettingsContext.Provider value={settings}>
<KeyboardContext.Provider value={keyboardValue}>
<MapStageProvider value={mapStageRef}>
<TokenDataContext.Provider value={tokenData}>
<ImageSourcesStateContext.Provider value={imageSources}>
<ImageSourcesUpdaterContext.Provider value={setImageSources}>
<InteractionEmitterContext.Provider
value={interactionEmitter}
>
<SetPreventMapInteractionContext.Provider
value={setPreventMapInteraction}
>
<StageWidthContext.Provider value={stageWidth}>
<StageHeightContext.Provider value={stageHeight}>
<MapWidthContext.Provider value={mapWidth}>
<MapHeightContext.Provider value={mapHeight}>
<StageScaleContext.Provider value={stageScale}>
<DebouncedStageScaleContext.Provider
value={debouncedStageScale}
>
<GridContext.Provider value={grid}>
<GridPixelSizeContext.Provider
value={gridPixelSize}
>
<GridCellPixelSizeContext.Provider
value={gridCellPixelSize}
>
<GridCellNormalizedSizeContext.Provider
value={gridCellNormalizedSize}
>
<GridOffsetContext.Provider
value={gridOffset}
>
<GridStrokeWidthContext.Provider
value={gridStrokeWidth}
>
<GridCellPixelOffsetContext.Provider
value={gridCellPixelOffset}
>
{children}
</GridCellPixelOffsetContext.Provider>
</GridStrokeWidthContext.Provider>
</GridOffsetContext.Provider>
</GridCellNormalizedSizeContext.Provider>
</GridCellPixelSizeContext.Provider>
</GridPixelSizeContext.Provider>
</GridContext.Provider>
</DebouncedStageScaleContext.Provider>
</StageScaleContext.Provider>
</MapHeightContext.Provider>
</MapWidthContext.Provider>
</StageHeightContext.Provider>
</StageWidthContext.Provider>
</SetPreventMapInteractionContext.Provider>
</InteractionEmitterContext.Provider>
</ImageSourcesUpdaterContext.Provider>
</ImageSourcesStateContext.Provider>
</TokenDataContext.Provider>
</MapStageProvider>
</KeyboardContext.Provider>
</SettingsContext.Provider>
</AuthContext.Provider>
);
}
export default KonvaBridge;