2020-10-22 16:10:31 +11:00
|
|
|
import React, { useState, useRef, 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";
|
|
|
|
|
|
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 useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
|
|
|
|
|
2021-02-05 11:00:31 +11:00
|
|
|
import { getGridDefaultInset, getGridMaxZoom } from "../../helpers/grid";
|
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);
|
|
|
|
|
|
2021-02-05 11:00:31 +11:00
|
|
|
const defaultInset = getGridDefaultInset(map.grid, map.width, map.height);
|
2020-10-08 19:20:14 +11:00
|
|
|
|
2020-09-24 16:54:33 +10:00
|
|
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
2020-10-22 16:10:31 +11:00
|
|
|
const mapStageRef = useRef();
|
2020-09-24 16:54:33 +10:00
|
|
|
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-22 16:10:31 +11:00
|
|
|
const containerRef = useRef();
|
|
|
|
|
usePreventOverscroll(containerRef);
|
|
|
|
|
|
|
|
|
|
const [mapWidth, mapHeight] = useImageCenter(
|
|
|
|
|
map,
|
|
|
|
|
mapStageRef,
|
|
|
|
|
stageWidth,
|
|
|
|
|
stageHeight,
|
|
|
|
|
stageTranslateRef,
|
|
|
|
|
setStageScale,
|
|
|
|
|
mapLayerRef,
|
|
|
|
|
containerRef,
|
|
|
|
|
true
|
|
|
|
|
);
|
2020-09-24 16:54:33 +10:00
|
|
|
|
2020-10-02 13:35:50 +10:00
|
|
|
const bind = useStageInteraction(
|
2020-10-22 16:10:31 +11:00
|
|
|
mapStageRef.current,
|
2020-10-02 13:35:50 +10:00
|
|
|
stageScale,
|
|
|
|
|
setStageScale,
|
2020-10-02 17:53:23 +10:00
|
|
|
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 17:53:23 +10:00
|
|
|
"pan",
|
|
|
|
|
preventMapInteraction
|
2020-10-02 13:35:50 +10:00
|
|
|
);
|
2020-09-24 16:54:33 +10:00
|
|
|
|
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;
|
2021-01-03 14:53:06 +11:00
|
|
|
|
|
|
|
|
const layout = useResponsiveLayout();
|
|
|
|
|
|
2020-09-24 16:54:33 +10:00
|
|
|
return (
|
|
|
|
|
<Box
|
|
|
|
|
sx={{
|
|
|
|
|
width: "100%",
|
2021-01-03 14:53:06 +11:00
|
|
|
height: layout.screenSize === "large" ? "500px" : "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 }}
|
2020-10-22 16:10:31 +11:00
|
|
|
ref={mapStageRef}
|
2020-09-24 16:54:33 +10:00
|
|
|
>
|
|
|
|
|
<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 && (
|
2020-10-22 16:10:31 +11:00
|
|
|
<>
|
|
|
|
|
<MapGrid map={map} strokeWidth={0.5} />
|
|
|
|
|
<MapGridEditor map={map} onGridChange={handleGridChange} />
|
|
|
|
|
</>
|
2020-10-10 18:31:53 +11:00
|
|
|
)}
|
|
|
|
|
</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;
|