2021-03-13 14:23:37 +11:00
|
|
|
import React, { useState, useRef } 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";
|
2021-03-13 14:23:37 +11:00
|
|
|
import KonvaBridge from "../../helpers/KonvaBridge";
|
2020-09-24 16:54:33 +10:00
|
|
|
|
2020-10-02 17:53:23 +10:00
|
|
|
import { MapInteractionProvider } from "../../contexts/MapInteractionContext";
|
2021-02-06 13:32:38 +11:00
|
|
|
import { GridProvider } from "../../contexts/GridContext";
|
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";
|
2021-07-16 14:55:33 +10:00
|
|
|
import { Map } from "../../types/Map";
|
|
|
|
|
import { GridInset } from "../../types/Grid";
|
2020-10-02 17:53:23 +10:00
|
|
|
|
2021-07-16 14:55:33 +10:00
|
|
|
type MapSettingsChangeEventHandler = (change: Partial<Map>) => void;
|
|
|
|
|
|
|
|
|
|
type MapEditorProps = {
|
|
|
|
|
map: Map;
|
|
|
|
|
onSettingsChange: MapSettingsChangeEventHandler;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function MapEditor({ map, onSettingsChange }: MapEditorProps) {
|
2021-04-22 16:53:35 +10:00
|
|
|
const [mapImage] = 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
|
|
|
|
2021-07-16 14:55:33 +10:00
|
|
|
function handleResize(width?: number, height?: number): void {
|
|
|
|
|
if (width) {
|
|
|
|
|
setStageWidth(width);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (height) {
|
|
|
|
|
setStageHeight(height);
|
|
|
|
|
}
|
2020-09-24 16:54:33 +10:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 14:55:33 +10:00
|
|
|
const containerRef = useRef(null);
|
2020-10-22 16:10:31 +11:00
|
|
|
usePreventOverscroll(containerRef);
|
|
|
|
|
|
|
|
|
|
const [mapWidth, mapHeight] = useImageCenter(
|
|
|
|
|
map,
|
|
|
|
|
mapStageRef,
|
|
|
|
|
stageWidth,
|
|
|
|
|
stageHeight,
|
|
|
|
|
stageTranslateRef,
|
|
|
|
|
setStageScale,
|
|
|
|
|
mapLayerRef,
|
|
|
|
|
containerRef,
|
|
|
|
|
true
|
|
|
|
|
);
|
2020-09-24 16:54:33 +10:00
|
|
|
|
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,
|
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),
|
2021-02-22 15:32:35 +11:00
|
|
|
"move",
|
2020-10-02 17:53:23 +10:00
|
|
|
preventMapInteraction
|
2020-10-02 13:35:50 +10:00
|
|
|
);
|
2020-09-24 16:54:33 +10:00
|
|
|
|
2021-07-16 14:55:33 +10:00
|
|
|
function handleGridChange(inset: GridInset) {
|
|
|
|
|
onSettingsChange({
|
|
|
|
|
grid: {
|
|
|
|
|
...map.grid,
|
|
|
|
|
inset,
|
|
|
|
|
},
|
2020-10-02 17:53:23 +10:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-06 20:43:25 +11:00
|
|
|
function handleMapReset() {
|
2021-07-16 14:55:33 +10:00
|
|
|
onSettingsChange({
|
|
|
|
|
grid: {
|
|
|
|
|
...map.grid,
|
|
|
|
|
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,
|
2021-03-13 14:23:37 +11:00
|
|
|
interactionEmitter: null,
|
2020-10-02 17:53:23 +10:00
|
|
|
};
|
|
|
|
|
|
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
|
|
|
|
2021-06-12 13:17:46 +10:00
|
|
|
const gridValid = map.grid.size.x !== 0 && map.grid.size.y !== 0;
|
|
|
|
|
|
2021-01-03 14:53:06 +11:00
|
|
|
const layout = useResponsiveLayout();
|
|
|
|
|
|
2020-09-24 16:54:33 +10:00
|
|
|
return (
|
2021-03-13 14:23:37 +11:00
|
|
|
<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}
|
2020-09-24 16:54:33 +10:00
|
|
|
>
|
2021-03-13 14:23:37 +11:00
|
|
|
<ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
|
|
|
|
|
<KonvaBridge
|
2021-07-16 14:55:33 +10:00
|
|
|
stageRender={(children: React.ReactNode) => (
|
2021-03-13 14:23:37 +11:00
|
|
|
<Stage
|
2021-07-16 14:55:33 +10:00
|
|
|
// @ts-ignore https://github.com/konvajs/react-konva/issues/342
|
2021-03-13 14:23:37 +11:00
|
|
|
width={stageWidth}
|
|
|
|
|
height={stageHeight}
|
|
|
|
|
scale={{ x: stageScale, y: stageScale }}
|
|
|
|
|
ref={mapStageRef}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</Stage>
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<Layer ref={mapLayerRef}>
|
2021-04-22 16:53:35 +10:00
|
|
|
<Image image={mapImage} width={mapWidth} height={mapHeight} />
|
2021-06-12 13:17:46 +10:00
|
|
|
{showGridControls && gridValid && (
|
2021-03-13 14:23:37 +11:00
|
|
|
<>
|
2021-02-09 16:58:14 +11:00
|
|
|
<MapGrid map={map} />
|
2020-10-22 16:10:31 +11:00
|
|
|
<MapGridEditor map={map} onGridChange={handleGridChange} />
|
2021-03-13 14:23:37 +11:00
|
|
|
</>
|
2020-10-10 18:31:53 +11:00
|
|
|
)}
|
2021-03-13 14:23:37 +11:00
|
|
|
</Layer>
|
|
|
|
|
</KonvaBridge>
|
|
|
|
|
</ReactResizeDetector>
|
2021-06-12 13:17:46 +10:00
|
|
|
{gridChanged && gridValid && (
|
2021-03-13 14:23:37 +11:00
|
|
|
<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>
|
|
|
|
|
)}
|
2021-06-06 10:24:56 +10:00
|
|
|
<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>
|
2021-03-13 14:23:37 +11:00
|
|
|
</Box>
|
|
|
|
|
</GridProvider>
|
|
|
|
|
</MapInteractionProvider>
|
2020-09-24 16:54:33 +10:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default MapEditor;
|