2020-10-10 18:31:53 +11:00
|
|
|
import React, { useState, useRef, useEffect, useContext } from "react";
|
2020-10-06 20:43:25 +11:00
|
|
|
import { Box, IconButton } from "theme-ui";
|
2020-09-24 16:54:33 +10:00
|
|
|
import { Stage, Layer, Image } from "react-konva";
|
|
|
|
|
import ReactResizeDetector from "react-resize-detector";
|
|
|
|
|
|
2020-10-02 13:35:50 +10:00
|
|
|
import useMapImage from "../../helpers/useMapImage";
|
2020-09-24 16:54:33 +10:00
|
|
|
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
2020-10-02 13:35:50 +10:00
|
|
|
import useStageInteraction from "../../helpers/useStageInteraction";
|
2020-10-22 12:51:37 +11:00
|
|
|
import { getMapDefaultInset, getMapMaxZoom } from "../../helpers/map";
|
2020-09-24 16:54:33 +10:00
|
|
|
|
2020-10-02 17:53:23 +10:00
|
|
|
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
2020-10-10 18:31:53 +11:00
|
|
|
import KeyboardContext from "../../contexts/KeyboardContext";
|
2020-10-02 17:53:23 +10:00
|
|
|
|
2020-10-06 20:43:25 +11:00
|
|
|
import ResetMapIcon from "../../icons/ResetMapIcon";
|
|
|
|
|
import GridOnIcon from "../../icons/GridOnIcon";
|
|
|
|
|
import GridOffIcon from "../../icons/GridOffIcon";
|
|
|
|
|
|
2020-10-02 17:53:23 +10:00
|
|
|
import MapGrid from "./MapGrid";
|
|
|
|
|
import MapGridEditor from "./MapGridEditor";
|
|
|
|
|
|
|
|
|
|
function MapEditor({ map, onSettingsChange }) {
|
2020-10-02 13:35:50 +10:00
|
|
|
const [mapImageSource] = useMapImage(map);
|
2020-09-24 16:54:33 +10:00
|
|
|
|
|
|
|
|
const [stageWidth, setStageWidth] = useState(1);
|
|
|
|
|
const [stageHeight, setStageHeight] = useState(1);
|
|
|
|
|
const [stageScale, setStageScale] = useState(1);
|
|
|
|
|
|
|
|
|
|
const stageRatio = stageWidth / stageHeight;
|
|
|
|
|
const mapRatio = map ? map.width / map.height : 1;
|
|
|
|
|
|
|
|
|
|
let mapWidth;
|
|
|
|
|
let mapHeight;
|
|
|
|
|
if (stageRatio > mapRatio) {
|
|
|
|
|
mapWidth = map ? stageHeight / (map.height / map.width) : stageWidth;
|
|
|
|
|
mapHeight = stageHeight;
|
|
|
|
|
} else {
|
|
|
|
|
mapWidth = stageWidth;
|
|
|
|
|
mapHeight = map ? stageWidth * (map.height / map.width) : stageHeight;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-08 19:20:14 +11:00
|
|
|
const defaultInset = getMapDefaultInset(
|
|
|
|
|
map.width,
|
|
|
|
|
map.height,
|
|
|
|
|
map.grid.size.x,
|
|
|
|
|
map.grid.size.y
|
|
|
|
|
);
|
|
|
|
|
|
2020-09-24 16:54:33 +10:00
|
|
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
|
|
|
|
const mapLayerRef = useRef();
|
2020-10-02 17:53:23 +10:00
|
|
|
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
2020-09-24 16:54:33 +10:00
|
|
|
|
|
|
|
|
function handleResize(width, height) {
|
|
|
|
|
setStageWidth(width);
|
|
|
|
|
setStageHeight(height);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-02 13:35:50 +10:00
|
|
|
// Reset map translate and scale
|
2020-09-24 16:54:33 +10:00
|
|
|
useEffect(() => {
|
|
|
|
|
const layer = mapLayerRef.current;
|
|
|
|
|
const containerRect = containerRef.current.getBoundingClientRect();
|
2020-10-02 13:35:50 +10:00
|
|
|
if (layer) {
|
2020-09-24 16:54:33 +10:00
|
|
|
let newTranslate;
|
|
|
|
|
if (stageRatio > mapRatio) {
|
|
|
|
|
newTranslate = {
|
|
|
|
|
x: -(mapWidth - containerRect.width) / 2,
|
|
|
|
|
y: 0,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
newTranslate = {
|
|
|
|
|
x: 0,
|
|
|
|
|
y: -(mapHeight - containerRect.height) / 2,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
layer.x(newTranslate.x);
|
|
|
|
|
layer.y(newTranslate.y);
|
|
|
|
|
layer.draw();
|
|
|
|
|
stageTranslateRef.current = newTranslate;
|
|
|
|
|
|
|
|
|
|
setStageScale(1);
|
|
|
|
|
}
|
2020-10-02 13:35:50 +10:00
|
|
|
}, [map.id, mapWidth, mapHeight, stageRatio, mapRatio]);
|
2020-09-24 16:54:33 +10:00
|
|
|
|
2020-10-02 13:35:50 +10:00
|
|
|
const bind = useStageInteraction(
|
|
|
|
|
mapLayerRef.current,
|
|
|
|
|
stageScale,
|
|
|
|
|
setStageScale,
|
2020-10-02 17:53:23 +10:00
|
|
|
stageTranslateRef,
|
2020-10-22 12:51:37 +11:00
|
|
|
getMapMaxZoom(map),
|
2020-10-02 17:53:23 +10:00
|
|
|
"pan",
|
|
|
|
|
preventMapInteraction
|
2020-10-02 13:35:50 +10:00
|
|
|
);
|
2020-09-24 16:54:33 +10:00
|
|
|
|
|
|
|
|
const containerRef = useRef();
|
|
|
|
|
usePreventOverscroll(containerRef);
|
|
|
|
|
|
2020-10-02 17:53:23 +10:00
|
|
|
function handleGridChange(inset) {
|
|
|
|
|
onSettingsChange("grid", {
|
|
|
|
|
...map.grid,
|
|
|
|
|
inset,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 20:43:25 +11:00
|
|
|
function handleMapReset() {
|
|
|
|
|
onSettingsChange("grid", {
|
|
|
|
|
...map.grid,
|
2020-10-08 19:20:14 +11:00
|
|
|
inset: defaultInset,
|
2020-10-06 20:43:25 +11:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [showGridControls, setShowGridControls] = useState(true);
|
|
|
|
|
|
2020-10-02 17:53:23 +10:00
|
|
|
const mapInteraction = {
|
|
|
|
|
stageScale,
|
|
|
|
|
stageWidth,
|
|
|
|
|
stageHeight,
|
|
|
|
|
setPreventMapInteraction,
|
|
|
|
|
mapWidth,
|
|
|
|
|
mapHeight,
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-10 18:31:53 +11:00
|
|
|
// Get keyboard context to pass to Konva
|
|
|
|
|
const keyboardValue = useContext(KeyboardContext);
|
|
|
|
|
|
2020-10-09 13:07:27 +11:00
|
|
|
const canEditGrid = map.type !== "default";
|
|
|
|
|
|
2020-10-06 20:43:25 +11:00
|
|
|
const gridChanged =
|
2020-10-08 19:20:14 +11:00
|
|
|
map.grid.inset.topLeft.x !== defaultInset.topLeft.x ||
|
|
|
|
|
map.grid.inset.topLeft.y !== defaultInset.topLeft.y ||
|
|
|
|
|
map.grid.inset.bottomRight.x !== defaultInset.bottomRight.x ||
|
|
|
|
|
map.grid.inset.bottomRight.y !== defaultInset.bottomRight.y;
|
2020-10-06 20:43:25 +11:00
|
|
|
|
2020-09-24 16:54:33 +10:00
|
|
|
return (
|
|
|
|
|
<Box
|
|
|
|
|
sx={{
|
|
|
|
|
width: "100%",
|
|
|
|
|
height: "300px",
|
2020-10-02 13:35:50 +10:00
|
|
|
cursor: "move",
|
2020-09-24 16:54:33 +10:00
|
|
|
touchAction: "none",
|
|
|
|
|
outline: "none",
|
2020-10-06 20:43:25 +11:00
|
|
|
position: "relative",
|
2020-09-24 16:54:33 +10:00
|
|
|
}}
|
|
|
|
|
bg="muted"
|
|
|
|
|
ref={containerRef}
|
|
|
|
|
{...bind()}
|
|
|
|
|
>
|
|
|
|
|
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
|
|
|
|
|
<Stage
|
|
|
|
|
width={stageWidth}
|
|
|
|
|
height={stageHeight}
|
|
|
|
|
scale={{ x: stageScale, y: stageScale }}
|
|
|
|
|
x={stageWidth / 2}
|
|
|
|
|
y={stageHeight / 2}
|
|
|
|
|
offset={{ x: stageWidth / 2, y: stageHeight / 2 }}
|
|
|
|
|
>
|
|
|
|
|
<Layer ref={mapLayerRef}>
|
2020-10-02 13:35:50 +10:00
|
|
|
<Image image={mapImageSource} width={mapWidth} height={mapHeight} />
|
2020-10-10 18:31:53 +11:00
|
|
|
<KeyboardContext.Provider value={keyboardValue}>
|
|
|
|
|
<MapInteractionProvider value={mapInteraction}>
|
|
|
|
|
{showGridControls && canEditGrid && (
|
|
|
|
|
<MapGrid map={map} strokeWidth={0.5} />
|
|
|
|
|
)}
|
|
|
|
|
{showGridControls && canEditGrid && (
|
|
|
|
|
<MapGridEditor map={map} onGridChange={handleGridChange} />
|
|
|
|
|
)}
|
|
|
|
|
</MapInteractionProvider>
|
|
|
|
|
</KeyboardContext.Provider>
|
2020-09-24 16:54:33 +10:00
|
|
|
</Layer>
|
|
|
|
|
</Stage>
|
|
|
|
|
</ReactResizeDetector>
|
2020-10-06 20:43:25 +11:00
|
|
|
{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>
|
|
|
|
|
)}
|
2020-10-09 13:07:27 +11:00
|
|
|
{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>
|
|
|
|
|
)}
|
2020-09-24 16:54:33 +10:00
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default MapEditor;
|