2020-05-25 18:56:46 +10:00
|
|
|
import React, { useRef, useEffect, useState, useContext } from "react";
|
2020-04-27 17:29:46 +10:00
|
|
|
import { Box } from "theme-ui";
|
2020-05-21 16:46:50 +10:00
|
|
|
import ReactResizeDetector from "react-resize-detector";
|
|
|
|
|
import { Stage, Layer, Image } from "react-konva";
|
2020-06-24 09:27:20 +10:00
|
|
|
import { EventEmitter } from "events";
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2021-02-04 15:06:34 +11:00
|
|
|
import useMapImage from "../../hooks/useMapImage";
|
|
|
|
|
import usePreventOverscroll from "../../hooks/usePreventOverscroll";
|
|
|
|
|
import useStageInteraction from "../../hooks/useStageInteraction";
|
|
|
|
|
import useImageCenter from "../../hooks/useImageCenter";
|
|
|
|
|
|
|
|
|
|
import { getGridMaxZoom } from "../../helpers/grid";
|
2020-04-29 18:21:44 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
2021-02-06 13:32:38 +11:00
|
|
|
import { MapStageProvider, useMapStage } from "../../contexts/MapStageContext";
|
|
|
|
|
import AuthContext, { useAuth } from "../../contexts/AuthContext";
|
|
|
|
|
import SettingsContext, { useSettings } from "../../contexts/SettingsContext";
|
2020-09-30 13:26:39 +10:00
|
|
|
import KeyboardContext from "../../contexts/KeyboardContext";
|
2021-02-08 16:53:56 +11:00
|
|
|
import TokenDataContext, {
|
|
|
|
|
useTokenData,
|
|
|
|
|
} from "../../contexts/TokenDataContext";
|
2021-02-06 13:32:38 +11:00
|
|
|
import { GridProvider } from "../../contexts/GridContext";
|
|
|
|
|
import { useKeyboard } from "../../contexts/KeyboardContext";
|
2021-03-12 17:35:26 +11:00
|
|
|
import {
|
|
|
|
|
ImageSourcesStateContext,
|
|
|
|
|
ImageSourcesUpdaterContext,
|
|
|
|
|
} from "../../contexts/ImageSourceContext";
|
2020-04-30 22:08:03 +10:00
|
|
|
|
2020-06-22 09:22:20 +10:00
|
|
|
function MapInteraction({
|
|
|
|
|
map,
|
2020-12-11 13:24:39 +11:00
|
|
|
mapState,
|
2020-06-22 09:22:20 +10:00
|
|
|
children,
|
|
|
|
|
controls,
|
|
|
|
|
selectedToolId,
|
|
|
|
|
onSelectedToolChange,
|
2020-06-26 12:32:51 +10:00
|
|
|
disabledControls,
|
2020-06-22 09:22:20 +10:00
|
|
|
}) {
|
2020-10-02 13:35:50 +10:00
|
|
|
const [mapImageSource, mapImageSourceStatus] = useMapImage(map);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-07-17 13:04:06 +10:00
|
|
|
// Map loaded taking in to account different resolutions
|
|
|
|
|
const [mapLoaded, setMapLoaded] = useState(false);
|
|
|
|
|
useEffect(() => {
|
2020-12-11 13:24:39 +11:00
|
|
|
if (
|
|
|
|
|
!map ||
|
2020-12-31 17:56:51 +11:00
|
|
|
!mapState ||
|
2020-12-11 13:24:39 +11:00
|
|
|
(map.type === "file" && !map.file && !map.resolutions) ||
|
|
|
|
|
mapState.mapId !== map.id
|
|
|
|
|
) {
|
2020-07-17 13:04:06 +10:00
|
|
|
setMapLoaded(false);
|
2020-10-13 07:10:51 +11:00
|
|
|
} else if (mapImageSourceStatus === "loaded") {
|
2020-07-17 13:04:06 +10:00
|
|
|
setMapLoaded(true);
|
|
|
|
|
}
|
2020-12-11 13:24:39 +11:00
|
|
|
}, [mapImageSourceStatus, map, mapState]);
|
2020-07-17 13:04:06 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const [stageWidth, setStageWidth] = useState(1);
|
|
|
|
|
const [stageHeight, setStageHeight] = useState(1);
|
|
|
|
|
const [stageScale, setStageScale] = useState(1);
|
|
|
|
|
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-25 14:09:45 +10:00
|
|
|
// Avoid state udpates when panning the map by using a ref and updating the konva element directly
|
|
|
|
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
2021-02-06 13:32:38 +11:00
|
|
|
const mapStageRef = useMapStage();
|
2020-10-22 16:10:31 +11:00
|
|
|
const mapLayerRef = useRef();
|
|
|
|
|
const mapImageRef = useRef();
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
function handleResize(width, height) {
|
2020-12-05 17:16:31 +11:00
|
|
|
if (width > 0 && height > 0) {
|
|
|
|
|
setStageWidth(width);
|
|
|
|
|
setStageHeight(height);
|
|
|
|
|
}
|
2020-05-21 16:46:50 +10:00
|
|
|
}
|
2020-04-30 15:45:20 +10:00
|
|
|
|
2020-10-22 16:10:31 +11:00
|
|
|
const containerRef = useRef();
|
|
|
|
|
usePreventOverscroll(containerRef);
|
|
|
|
|
|
|
|
|
|
const [mapWidth, mapHeight] = useImageCenter(
|
|
|
|
|
map,
|
|
|
|
|
mapStageRef,
|
|
|
|
|
stageWidth,
|
|
|
|
|
stageHeight,
|
|
|
|
|
stageTranslateRef,
|
|
|
|
|
setStageScale,
|
|
|
|
|
mapLayerRef,
|
|
|
|
|
containerRef
|
|
|
|
|
);
|
2020-10-02 13:35:50 +10:00
|
|
|
|
|
|
|
|
const previousSelectedToolRef = useRef(selectedToolId);
|
|
|
|
|
|
|
|
|
|
const [interactionEmitter] = useState(new EventEmitter());
|
|
|
|
|
|
2021-02-07 20:44:30 +11:00
|
|
|
useStageInteraction(
|
2020-10-22 16:10:31 +11:00
|
|
|
mapStageRef.current,
|
2020-10-02 13:35:50 +10:00
|
|
|
stageScale,
|
|
|
|
|
setStageScale,
|
|
|
|
|
stageTranslateRef,
|
2020-10-22 16:10:31 +11:00
|
|
|
mapLayerRef.current,
|
2021-02-04 15:06:34 +11:00
|
|
|
getGridMaxZoom(map?.grid),
|
2020-10-02 13:44:15 +10:00
|
|
|
selectedToolId,
|
2020-10-02 13:35:50 +10:00
|
|
|
preventMapInteraction,
|
|
|
|
|
{
|
|
|
|
|
onPinchStart: () => {
|
2021-02-22 15:32:35 +11:00
|
|
|
// Change to move tool when pinching and zooming
|
2020-10-02 13:35:50 +10:00
|
|
|
previousSelectedToolRef.current = selectedToolId;
|
2021-02-22 15:32:35 +11:00
|
|
|
onSelectedToolChange("move");
|
2020-10-02 13:35:50 +10:00
|
|
|
},
|
|
|
|
|
onPinchEnd: () => {
|
|
|
|
|
onSelectedToolChange(previousSelectedToolRef.current);
|
|
|
|
|
},
|
|
|
|
|
onDrag: ({ first, last }) => {
|
|
|
|
|
if (first) {
|
|
|
|
|
interactionEmitter.emit("dragStart");
|
|
|
|
|
} else if (last) {
|
|
|
|
|
interactionEmitter.emit("dragEnd");
|
|
|
|
|
} else {
|
|
|
|
|
interactionEmitter.emit("drag");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2020-09-30 13:26:39 +10:00
|
|
|
function handleKeyDown(event) {
|
2021-02-22 15:32:35 +11:00
|
|
|
// Change to move tool when pressing space
|
|
|
|
|
if (event.key === " " && selectedToolId === "move") {
|
|
|
|
|
// Stop active state on move icon from being selected
|
2020-09-30 13:26:39 +10:00
|
|
|
event.preventDefault();
|
2020-06-24 18:05:33 +10:00
|
|
|
}
|
2020-09-30 13:26:39 +10:00
|
|
|
if (
|
|
|
|
|
event.key === " " &&
|
2021-02-22 15:32:35 +11:00
|
|
|
selectedToolId !== "move" &&
|
|
|
|
|
!disabledControls.includes("move")
|
2020-09-30 13:26:39 +10:00
|
|
|
) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
previousSelectedToolRef.current = selectedToolId;
|
2021-02-22 15:32:35 +11:00
|
|
|
onSelectedToolChange("move");
|
2020-06-26 12:32:51 +10:00
|
|
|
}
|
|
|
|
|
|
2020-09-30 13:26:39 +10:00
|
|
|
// Basic keyboard shortcuts
|
2021-02-22 15:32:35 +11:00
|
|
|
if (event.key === "w" && !disabledControls.includes("move")) {
|
|
|
|
|
onSelectedToolChange("move");
|
2020-09-30 13:26:39 +10:00
|
|
|
}
|
|
|
|
|
if (event.key === "d" && !disabledControls.includes("drawing")) {
|
|
|
|
|
onSelectedToolChange("drawing");
|
|
|
|
|
}
|
|
|
|
|
if (event.key === "f" && !disabledControls.includes("fog")) {
|
|
|
|
|
onSelectedToolChange("fog");
|
2020-06-24 18:05:33 +10:00
|
|
|
}
|
2020-09-30 13:26:39 +10:00
|
|
|
if (event.key === "m" && !disabledControls.includes("measure")) {
|
|
|
|
|
onSelectedToolChange("measure");
|
|
|
|
|
}
|
|
|
|
|
if (event.key === "q" && !disabledControls.includes("pointer")) {
|
|
|
|
|
onSelectedToolChange("pointer");
|
|
|
|
|
}
|
2020-11-03 16:21:39 +11:00
|
|
|
if (event.key === "n" && !disabledControls.includes("note")) {
|
|
|
|
|
onSelectedToolChange("note");
|
|
|
|
|
}
|
2020-09-30 13:26:39 +10:00
|
|
|
}
|
2020-07-18 11:06:02 +10:00
|
|
|
|
2020-09-30 13:26:39 +10:00
|
|
|
function handleKeyUp(event) {
|
2021-02-22 15:32:35 +11:00
|
|
|
if (event.key === " " && selectedToolId === "move") {
|
2020-09-30 13:26:39 +10:00
|
|
|
onSelectedToolChange(previousSelectedToolRef.current);
|
2020-07-18 11:06:02 +10:00
|
|
|
}
|
2020-09-30 13:26:39 +10:00
|
|
|
}
|
2020-07-18 11:06:02 +10:00
|
|
|
|
2020-09-30 13:26:39 +10:00
|
|
|
useKeyboard(handleKeyDown, handleKeyUp);
|
|
|
|
|
// Get keyboard context to pass to Konva
|
|
|
|
|
const keyboardValue = useContext(KeyboardContext);
|
2020-06-24 18:05:33 +10:00
|
|
|
|
2020-05-22 15:10:20 +10:00
|
|
|
function getCursorForTool(tool) {
|
|
|
|
|
switch (tool) {
|
2021-02-22 15:32:35 +11:00
|
|
|
case "move":
|
2020-05-22 15:10:20 +10:00
|
|
|
return "move";
|
|
|
|
|
case "fog":
|
2020-06-21 11:01:03 +10:00
|
|
|
case "drawing":
|
2020-11-03 16:21:39 +11:00
|
|
|
return settings.settings[tool].type === "move"
|
|
|
|
|
? "pointer"
|
|
|
|
|
: "crosshair";
|
2020-06-26 12:33:07 +10:00
|
|
|
case "measure":
|
2020-07-31 14:49:36 +10:00
|
|
|
case "pointer":
|
2020-11-05 12:28:28 +11:00
|
|
|
case "note":
|
2020-05-22 15:10:20 +10:00
|
|
|
return "crosshair";
|
|
|
|
|
default:
|
|
|
|
|
return "default";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-06 13:32:38 +11:00
|
|
|
const auth = useAuth();
|
|
|
|
|
const settings = useSettings();
|
2021-02-08 16:53:56 +11:00
|
|
|
const tokenData = useTokenData();
|
2021-03-12 17:35:26 +11:00
|
|
|
const imageSources = useContext(ImageSourcesStateContext);
|
|
|
|
|
const setImageSources = useContext(ImageSourcesUpdaterContext);
|
2020-04-27 17:29:46 +10:00
|
|
|
|
2020-05-21 16:46:50 +10:00
|
|
|
const mapInteraction = {
|
|
|
|
|
stageScale,
|
|
|
|
|
stageWidth,
|
|
|
|
|
stageHeight,
|
|
|
|
|
setPreventMapInteraction,
|
|
|
|
|
mapWidth,
|
|
|
|
|
mapHeight,
|
2020-06-24 09:27:20 +10:00
|
|
|
interactionEmitter,
|
2020-05-21 16:46:50 +10:00
|
|
|
};
|
2020-04-27 17:29:46 +10:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Box
|
2020-05-22 15:10:20 +10:00
|
|
|
sx={{
|
|
|
|
|
flexGrow: 1,
|
|
|
|
|
position: "relative",
|
|
|
|
|
cursor: getCursorForTool(selectedToolId),
|
2020-05-22 23:43:55 +10:00
|
|
|
touchAction: "none",
|
2020-06-24 18:05:33 +10:00
|
|
|
outline: "none",
|
2020-05-22 15:10:20 +10:00
|
|
|
}}
|
2020-05-21 16:46:50 +10:00
|
|
|
ref={containerRef}
|
2020-04-27 17:29:46 +10:00
|
|
|
className="map"
|
|
|
|
|
>
|
2020-05-21 16:46:50 +10:00
|
|
|
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
|
|
|
|
|
<Stage
|
|
|
|
|
width={stageWidth}
|
|
|
|
|
height={stageHeight}
|
|
|
|
|
scale={{ x: stageScale, y: stageScale }}
|
|
|
|
|
ref={mapStageRef}
|
|
|
|
|
>
|
2020-05-25 14:09:45 +10:00
|
|
|
<Layer ref={mapLayerRef}>
|
2020-05-21 16:46:50 +10:00
|
|
|
<Image
|
2020-10-02 13:35:50 +10:00
|
|
|
image={mapLoaded && mapImageSource}
|
2020-05-21 16:46:50 +10:00
|
|
|
width={mapWidth}
|
|
|
|
|
height={mapHeight}
|
|
|
|
|
id="mapImage"
|
2020-05-22 13:47:11 +10:00
|
|
|
ref={mapImageRef}
|
2020-05-18 21:52:46 +10:00
|
|
|
/>
|
2020-05-21 16:46:50 +10:00
|
|
|
{/* Forward auth context to konva elements */}
|
|
|
|
|
<AuthContext.Provider value={auth}>
|
2021-01-01 12:37:54 +11:00
|
|
|
<SettingsContext.Provider value={settings}>
|
|
|
|
|
<KeyboardContext.Provider value={keyboardValue}>
|
|
|
|
|
<MapInteractionProvider value={mapInteraction}>
|
2021-02-06 13:32:38 +11:00
|
|
|
<GridProvider
|
|
|
|
|
grid={map?.grid}
|
|
|
|
|
width={mapWidth}
|
|
|
|
|
height={mapHeight}
|
|
|
|
|
>
|
|
|
|
|
<MapStageProvider value={mapStageRef}>
|
2021-02-08 16:53:56 +11:00
|
|
|
<TokenDataContext.Provider value={tokenData}>
|
2021-03-12 17:35:26 +11:00
|
|
|
<ImageSourcesStateContext.Provider
|
|
|
|
|
value={imageSources}
|
|
|
|
|
>
|
|
|
|
|
<ImageSourcesUpdaterContext.Provider
|
|
|
|
|
value={setImageSources}
|
|
|
|
|
>
|
|
|
|
|
{mapLoaded && children}
|
|
|
|
|
</ImageSourcesUpdaterContext.Provider>
|
|
|
|
|
</ImageSourcesStateContext.Provider>
|
2021-02-08 16:53:56 +11:00
|
|
|
</TokenDataContext.Provider>
|
2021-02-06 13:32:38 +11:00
|
|
|
</MapStageProvider>
|
|
|
|
|
</GridProvider>
|
2021-01-01 12:37:54 +11:00
|
|
|
</MapInteractionProvider>
|
|
|
|
|
</KeyboardContext.Provider>
|
|
|
|
|
</SettingsContext.Provider>
|
2020-05-21 16:46:50 +10:00
|
|
|
</AuthContext.Provider>
|
|
|
|
|
</Layer>
|
|
|
|
|
</Stage>
|
|
|
|
|
</ReactResizeDetector>
|
|
|
|
|
<MapInteractionProvider value={mapInteraction}>
|
2021-02-06 13:32:38 +11:00
|
|
|
<GridProvider grid={map?.grid} width={mapWidth} height={mapHeight}>
|
|
|
|
|
{controls}
|
|
|
|
|
</GridProvider>
|
2020-05-21 16:46:50 +10:00
|
|
|
</MapInteractionProvider>
|
2020-04-27 17:29:46 +10:00
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default MapInteraction;
|