Added separate edit map modal and basic map viewer for it
This commit is contained in:
parent
2c0a01b66c
commit
78c86e6194
184
src/components/map/MapEditor.js
Normal file
184
src/components/map/MapEditor.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
import { Box } from "theme-ui";
|
||||||
|
import { Stage, Layer, Image } from "react-konva";
|
||||||
|
import ReactResizeDetector from "react-resize-detector";
|
||||||
|
import useImage from "use-image";
|
||||||
|
import { useGesture } from "react-use-gesture";
|
||||||
|
import normalizeWheel from "normalize-wheel";
|
||||||
|
|
||||||
|
import useDataSource from "../../helpers/useDataSource";
|
||||||
|
import usePreventOverscroll from "../../helpers/usePreventOverscroll";
|
||||||
|
|
||||||
|
import { mapSources as defaultMapSources } from "../../maps";
|
||||||
|
|
||||||
|
const wheelZoomSpeed = -0.001;
|
||||||
|
const touchZoomSpeed = 0.005;
|
||||||
|
const minZoom = 0.1;
|
||||||
|
const maxZoom = 5;
|
||||||
|
|
||||||
|
function MapEditor({ map }) {
|
||||||
|
const mapSource = useDataSource(map, defaultMapSources);
|
||||||
|
const [mapSourceImage] = useImage(mapSource);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
||||||
|
const isInteractingWithCanvas = useRef(false);
|
||||||
|
const pinchPreviousDistanceRef = useRef();
|
||||||
|
const pinchPreviousOriginRef = useRef();
|
||||||
|
const mapLayerRef = useRef();
|
||||||
|
|
||||||
|
function handleResize(width, height) {
|
||||||
|
setStageWidth(width);
|
||||||
|
setStageHeight(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const layer = mapLayerRef.current;
|
||||||
|
const containerRect = containerRef.current.getBoundingClientRect();
|
||||||
|
if (map && layer) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}, [map, mapWidth, mapHeight, stageRatio, mapRatio]);
|
||||||
|
|
||||||
|
const bind = useGesture({
|
||||||
|
onWheelStart: ({ event }) => {
|
||||||
|
isInteractingWithCanvas.current =
|
||||||
|
event.target === mapLayerRef.current.getCanvas()._canvas;
|
||||||
|
},
|
||||||
|
onWheel: ({ event }) => {
|
||||||
|
event.persist();
|
||||||
|
const { pixelY } = normalizeWheel(event);
|
||||||
|
if (!isInteractingWithCanvas.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newScale = Math.min(
|
||||||
|
Math.max(stageScale + pixelY * wheelZoomSpeed, minZoom),
|
||||||
|
maxZoom
|
||||||
|
);
|
||||||
|
setStageScale(newScale);
|
||||||
|
},
|
||||||
|
onPinch: ({ da, origin, first }) => {
|
||||||
|
const [distance] = da;
|
||||||
|
const [originX, originY] = origin;
|
||||||
|
if (first) {
|
||||||
|
pinchPreviousDistanceRef.current = distance;
|
||||||
|
pinchPreviousOriginRef.current = { x: originX, y: originY };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply scale
|
||||||
|
const distanceDelta = distance - pinchPreviousDistanceRef.current;
|
||||||
|
const originXDelta = originX - pinchPreviousOriginRef.current.x;
|
||||||
|
const originYDelta = originY - pinchPreviousOriginRef.current.y;
|
||||||
|
const newScale = Math.min(
|
||||||
|
Math.max(stageScale + distanceDelta * touchZoomSpeed, minZoom),
|
||||||
|
maxZoom
|
||||||
|
);
|
||||||
|
setStageScale(newScale);
|
||||||
|
|
||||||
|
// Apply translate
|
||||||
|
const stageTranslate = stageTranslateRef.current;
|
||||||
|
const layer = mapLayerRef.current;
|
||||||
|
const newTranslate = {
|
||||||
|
x: stageTranslate.x + originXDelta / newScale,
|
||||||
|
y: stageTranslate.y + originYDelta / newScale,
|
||||||
|
};
|
||||||
|
layer.x(newTranslate.x);
|
||||||
|
layer.y(newTranslate.y);
|
||||||
|
layer.draw();
|
||||||
|
stageTranslateRef.current = newTranslate;
|
||||||
|
|
||||||
|
pinchPreviousDistanceRef.current = distance;
|
||||||
|
pinchPreviousOriginRef.current = { x: originX, y: originY };
|
||||||
|
},
|
||||||
|
onDragStart: ({ event }) => {
|
||||||
|
isInteractingWithCanvas.current =
|
||||||
|
event.target === mapLayerRef.current.getCanvas()._canvas;
|
||||||
|
},
|
||||||
|
onDrag: ({ delta, pinching }) => {
|
||||||
|
if (pinching || !isInteractingWithCanvas.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [dx, dy] = delta;
|
||||||
|
const stageTranslate = stageTranslateRef.current;
|
||||||
|
const layer = mapLayerRef.current;
|
||||||
|
const newTranslate = {
|
||||||
|
x: stageTranslate.x + dx / stageScale,
|
||||||
|
y: stageTranslate.y + dy / stageScale,
|
||||||
|
};
|
||||||
|
layer.x(newTranslate.x);
|
||||||
|
layer.y(newTranslate.y);
|
||||||
|
layer.draw();
|
||||||
|
stageTranslateRef.current = newTranslate;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useRef();
|
||||||
|
usePreventOverscroll(containerRef);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "300px",
|
||||||
|
cursor: "pointer",
|
||||||
|
touchAction: "none",
|
||||||
|
outline: "none",
|
||||||
|
}}
|
||||||
|
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}>
|
||||||
|
<Image image={mapSourceImage} width={mapWidth} height={mapHeight} />
|
||||||
|
</Layer>
|
||||||
|
</Stage>
|
||||||
|
</ReactResizeDetector>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MapEditor;
|
@ -81,18 +81,18 @@ function MapSettings({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Box mt={2} sx={{ flexGrow: 1 }}>
|
||||||
|
<Label htmlFor="name">Name</Label>
|
||||||
|
<Input
|
||||||
|
name="name"
|
||||||
|
value={(map && map.name) || ""}
|
||||||
|
onChange={(e) => onSettingsChange("name", e.target.value)}
|
||||||
|
disabled={mapEmpty || map.type === "default"}
|
||||||
|
my={1}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
{showMore && (
|
{showMore && (
|
||||||
<>
|
<>
|
||||||
<Box mt={2} sx={{ flexGrow: 1 }}>
|
|
||||||
<Label htmlFor="name">Name</Label>
|
|
||||||
<Input
|
|
||||||
name="name"
|
|
||||||
value={(map && map.name) || ""}
|
|
||||||
onChange={(e) => onSettingsChange("name", e.target.value)}
|
|
||||||
disabled={mapEmpty || map.type === "default"}
|
|
||||||
my={1}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Flex
|
<Flex
|
||||||
mt={2}
|
mt={2}
|
||||||
mb={mapEmpty || map.type === "default" ? 2 : 0}
|
mb={mapEmpty || map.type === "default" ? 2 : 0}
|
||||||
|
@ -1,23 +1,12 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Flex, Image as UIImage, IconButton, Box, Text } from "theme-ui";
|
import { Flex, Image as UIImage, IconButton, Box, Text } from "theme-ui";
|
||||||
|
|
||||||
import RemoveMapIcon from "../../icons/RemoveMapIcon";
|
import EditMapIcon from "../../icons/EditMapIcon";
|
||||||
import ResetMapIcon from "../../icons/ResetMapIcon";
|
|
||||||
import ExpandMoreDotIcon from "../../icons/ExpandMoreDotIcon";
|
|
||||||
|
|
||||||
import useDataSource from "../../helpers/useDataSource";
|
import useDataSource from "../../helpers/useDataSource";
|
||||||
import { mapSources as defaultMapSources, unknownSource } from "../../maps";
|
import { mapSources as defaultMapSources, unknownSource } from "../../maps";
|
||||||
|
|
||||||
function MapTile({
|
function MapTile({ map, isSelected, onMapSelect, onMapEdit, onDone, large }) {
|
||||||
map,
|
|
||||||
mapState,
|
|
||||||
isSelected,
|
|
||||||
onMapSelect,
|
|
||||||
onMapRemove,
|
|
||||||
onMapReset,
|
|
||||||
onDone,
|
|
||||||
large,
|
|
||||||
}) {
|
|
||||||
const [isMapTileMenuOpen, setIsTileMenuOpen] = useState(false);
|
const [isMapTileMenuOpen, setIsTileMenuOpen] = useState(false);
|
||||||
const isDefault = map.type === "default";
|
const isDefault = map.type === "default";
|
||||||
const mapSource = useDataSource(
|
const mapSource = useDataSource(
|
||||||
@ -30,69 +19,6 @@ function MapTile({
|
|||||||
unknownSource
|
unknownSource
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasMapState =
|
|
||||||
mapState &&
|
|
||||||
(Object.values(mapState.tokens).length > 0 ||
|
|
||||||
mapState.mapDrawActions.length > 0 ||
|
|
||||||
mapState.fogDrawActions.length > 0);
|
|
||||||
|
|
||||||
const expandButton = (
|
|
||||||
<IconButton
|
|
||||||
aria-label="Show Map Actions"
|
|
||||||
title="Show Map Actions"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsTileMenuOpen(true);
|
|
||||||
}}
|
|
||||||
bg="overlay"
|
|
||||||
sx={{ borderRadius: "50%" }}
|
|
||||||
m={2}
|
|
||||||
>
|
|
||||||
<ExpandMoreDotIcon />
|
|
||||||
</IconButton>
|
|
||||||
);
|
|
||||||
|
|
||||||
function removeButton(map) {
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
aria-label="Remove Map"
|
|
||||||
title="Remove Map"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsTileMenuOpen(false);
|
|
||||||
onMapRemove(map.id);
|
|
||||||
}}
|
|
||||||
bg="overlay"
|
|
||||||
sx={{ borderRadius: "50%" }}
|
|
||||||
m={2}
|
|
||||||
>
|
|
||||||
<RemoveMapIcon />
|
|
||||||
</IconButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetButton(map) {
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
aria-label="Reset Map"
|
|
||||||
title="Reset Map"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsTileMenuOpen(false);
|
|
||||||
onMapReset(map.id);
|
|
||||||
}}
|
|
||||||
bg="overlay"
|
|
||||||
sx={{ borderRadius: "50%" }}
|
|
||||||
m={2}
|
|
||||||
>
|
|
||||||
<ResetMapIcon />
|
|
||||||
</IconButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
key={map.id}
|
key={map.id}
|
||||||
@ -174,30 +100,22 @@ function MapTile({
|
|||||||
{/* Show expand button only if both reset and remove is available */}
|
{/* Show expand button only if both reset and remove is available */}
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<Box sx={{ position: "absolute", top: 0, right: 0 }}>
|
<Box sx={{ position: "absolute", top: 0, right: 0 }}>
|
||||||
{isDefault && hasMapState && resetButton(map)}
|
<IconButton
|
||||||
{!isDefault && hasMapState && !isMapTileMenuOpen && expandButton}
|
aria-label="Edit Map"
|
||||||
{!isDefault && !hasMapState && removeButton(map)}
|
title="Edit Map"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onMapEdit(map.id);
|
||||||
|
}}
|
||||||
|
bg="overlay"
|
||||||
|
sx={{ borderRadius: "50%" }}
|
||||||
|
m={2}
|
||||||
|
>
|
||||||
|
<EditMapIcon />
|
||||||
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{/* Tile menu for two actions */}
|
|
||||||
{!isDefault && isMapTileMenuOpen && isSelected && (
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
bg="muted"
|
|
||||||
onClick={() => setIsTileMenuOpen(false)}
|
|
||||||
>
|
|
||||||
{!isDefault && removeButton(map)}
|
|
||||||
{hasMapState && resetButton(map)}
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { Flex, Box, Text } from "theme-ui";
|
import { Flex, Box, Text, IconButton, Close } from "theme-ui";
|
||||||
import SimpleBar from "simplebar-react";
|
import SimpleBar from "simplebar-react";
|
||||||
import { useMedia } from "react-media";
|
import { useMedia } from "react-media";
|
||||||
|
|
||||||
import AddIcon from "../../icons/AddIcon";
|
import AddIcon from "../../icons/AddIcon";
|
||||||
|
import RemoveMapIcon from "../../icons/RemoveMapIcon";
|
||||||
|
import ResetMapIcon from "../../icons/ResetMapIcon";
|
||||||
|
|
||||||
import MapTile from "./MapTile";
|
import MapTile from "./MapTile";
|
||||||
import Link from "../Link";
|
import Link from "../Link";
|
||||||
@ -15,19 +17,27 @@ function MapTiles({
|
|||||||
selectedMap,
|
selectedMap,
|
||||||
selectedMapState,
|
selectedMapState,
|
||||||
onMapSelect,
|
onMapSelect,
|
||||||
onMapAdd,
|
|
||||||
onMapRemove,
|
onMapRemove,
|
||||||
onMapReset,
|
onMapReset,
|
||||||
|
onMapAdd,
|
||||||
|
onMapEdit,
|
||||||
onDone,
|
onDone,
|
||||||
}) {
|
}) {
|
||||||
const { databaseStatus } = useContext(DatabaseContext);
|
const { databaseStatus } = useContext(DatabaseContext);
|
||||||
const isSmallScreen = useMedia({ query: "(max-width: 500px)" });
|
const isSmallScreen = useMedia({ query: "(max-width: 500px)" });
|
||||||
|
|
||||||
|
const hasMapState =
|
||||||
|
selectedMapState &&
|
||||||
|
(Object.values(selectedMapState.tokens).length > 0 ||
|
||||||
|
selectedMapState.mapDrawActions.length > 0 ||
|
||||||
|
selectedMapState.fogDrawActions.length > 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: "relative" }}>
|
<Box sx={{ position: "relative" }}>
|
||||||
<SimpleBar style={{ maxHeight: "300px" }}>
|
<SimpleBar style={{ maxHeight: "400px" }}>
|
||||||
<Flex
|
<Flex
|
||||||
p={2}
|
p={2}
|
||||||
|
pb={4}
|
||||||
bg="muted"
|
bg="muted"
|
||||||
sx={{
|
sx={{
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
@ -79,14 +89,10 @@ function MapTiles({
|
|||||||
return (
|
return (
|
||||||
<MapTile
|
<MapTile
|
||||||
key={map.id}
|
key={map.id}
|
||||||
// TODO: Move to selected map here and fix url error
|
|
||||||
// when done is clicked
|
|
||||||
map={map}
|
map={map}
|
||||||
mapState={isSelected && selectedMapState}
|
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onMapSelect={onMapSelect}
|
onMapSelect={onMapSelect}
|
||||||
onMapRemove={onMapRemove}
|
onMapEdit={onMapEdit}
|
||||||
onMapReset={onMapReset}
|
|
||||||
onDone={onDone}
|
onDone={onDone}
|
||||||
large={isSmallScreen}
|
large={isSmallScreen}
|
||||||
/>
|
/>
|
||||||
@ -112,6 +118,41 @@ function MapTiles({
|
|||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
{selectedMap && (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
bg="overlay"
|
||||||
|
>
|
||||||
|
<Close
|
||||||
|
title="Clear Selection"
|
||||||
|
aria-label="Clear Selection"
|
||||||
|
onClick={() => onMapSelect(null)}
|
||||||
|
/>
|
||||||
|
<Flex>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Reset Map"
|
||||||
|
title="Reset Map"
|
||||||
|
onClick={() => onMapReset(selectedMap.id)}
|
||||||
|
disabled={!hasMapState}
|
||||||
|
>
|
||||||
|
<ResetMapIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Remove Map"
|
||||||
|
title="Remove Map"
|
||||||
|
onClick={() => onMapRemove(selectedMap.id)}
|
||||||
|
>
|
||||||
|
<RemoveMapIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
18
src/icons/EditMapIcon.js
Normal file
18
src/icons/EditMapIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function EditMapIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path d="M3 17.46v3.04c0 .28.22.5.5.5h3.04c.13 0 .26-.05.35-.15L17.81 9.94l-3.75-3.75L3.15 17.1c-.1.1-.15.22-.15.36zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditMapIcon;
|
105
src/modals/EditMapModal.js
Normal file
105
src/modals/EditMapModal.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import React, { useState, useContext } from "react";
|
||||||
|
import { Button, Flex, Label } from "theme-ui";
|
||||||
|
|
||||||
|
import Modal from "../components/Modal";
|
||||||
|
import MapSettings from "../components/map/MapSettings";
|
||||||
|
import MapEditor from "../components/map/MapEditor";
|
||||||
|
|
||||||
|
import MapDataContext from "../contexts/MapDataContext";
|
||||||
|
|
||||||
|
import { isEmpty } from "../helpers/shared";
|
||||||
|
|
||||||
|
function SelectMapModal({ isOpen, onDone, map, mapState }) {
|
||||||
|
const { updateMap, updateMapState } = useContext(MapDataContext);
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
onDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSave() {
|
||||||
|
await applyMapChanges();
|
||||||
|
onDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map settings
|
||||||
|
*/
|
||||||
|
// Local cache of map setting changes
|
||||||
|
// Applied when done is clicked or map selection is changed
|
||||||
|
const [mapSettingChanges, setMapSettingChanges] = useState({});
|
||||||
|
const [mapStateSettingChanges, setMapStateSettingChanges] = useState({});
|
||||||
|
|
||||||
|
function handleMapSettingsChange(key, value) {
|
||||||
|
setMapSettingChanges((prevChanges) => ({
|
||||||
|
...prevChanges,
|
||||||
|
[key]: value,
|
||||||
|
lastModified: Date.now(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMapStateSettingsChange(key, value) {
|
||||||
|
setMapStateSettingChanges((prevChanges) => ({
|
||||||
|
...prevChanges,
|
||||||
|
[key]: value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyMapChanges() {
|
||||||
|
if (!isEmpty(mapSettingChanges) || !isEmpty(mapStateSettingChanges)) {
|
||||||
|
// Ensure grid values are positive
|
||||||
|
let verifiedChanges = { ...mapSettingChanges };
|
||||||
|
if ("gridX" in verifiedChanges) {
|
||||||
|
verifiedChanges.gridX = verifiedChanges.gridX || 1;
|
||||||
|
}
|
||||||
|
if ("gridY" in verifiedChanges) {
|
||||||
|
verifiedChanges.gridY = verifiedChanges.gridY || 1;
|
||||||
|
}
|
||||||
|
await updateMap(map.id, verifiedChanges);
|
||||||
|
await updateMapState(map.id, mapStateSettingChanges);
|
||||||
|
|
||||||
|
setMapSettingChanges({});
|
||||||
|
setMapStateSettingChanges({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedMapWithChanges = map && {
|
||||||
|
...map,
|
||||||
|
...mapSettingChanges,
|
||||||
|
};
|
||||||
|
const selectedMapStateWithChanges = mapState && {
|
||||||
|
...mapState,
|
||||||
|
...mapStateSettingChanges,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [showMoreSettings, setShowMoreSettings] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onRequestClose={handleClose}
|
||||||
|
style={{ maxWidth: "542px", width: "calc(100% - 16px)" }}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label pt={2} pb={1}>
|
||||||
|
Edit map
|
||||||
|
</Label>
|
||||||
|
<MapEditor map={selectedMapWithChanges} />
|
||||||
|
<MapSettings
|
||||||
|
map={selectedMapWithChanges}
|
||||||
|
mapState={selectedMapStateWithChanges}
|
||||||
|
onSettingsChange={handleMapSettingsChange}
|
||||||
|
onStateSettingsChange={handleMapStateSettingsChange}
|
||||||
|
showMore={showMoreSettings}
|
||||||
|
onShowMoreChange={setShowMoreSettings}
|
||||||
|
/>
|
||||||
|
<Button onClick={handleSave}>Save</Button>
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectMapModal;
|
@ -2,9 +2,10 @@ import React, { useRef, useState, useContext } from "react";
|
|||||||
import { Button, Flex, Label } from "theme-ui";
|
import { Button, Flex, Label } from "theme-ui";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
|
||||||
|
import EditMapModal from "./EditMapModal";
|
||||||
|
|
||||||
import Modal from "../components/Modal";
|
import Modal from "../components/Modal";
|
||||||
import MapTiles from "../components/map/MapTiles";
|
import MapTiles from "../components/map/MapTiles";
|
||||||
import MapSettings from "../components/map/MapSettings";
|
|
||||||
import ImageDrop from "../components/ImageDrop";
|
import ImageDrop from "../components/ImageDrop";
|
||||||
import LoadingOverlay from "../components/LoadingOverlay";
|
import LoadingOverlay from "../components/LoadingOverlay";
|
||||||
|
|
||||||
@ -13,7 +14,6 @@ import blobToBuffer from "../helpers/blobToBuffer";
|
|||||||
import MapDataContext from "../contexts/MapDataContext";
|
import MapDataContext from "../contexts/MapDataContext";
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
|
|
||||||
import { isEmpty } from "../helpers/shared";
|
|
||||||
import { resizeImage } from "../helpers/image";
|
import { resizeImage } from "../helpers/image";
|
||||||
|
|
||||||
const defaultMapSize = 22;
|
const defaultMapSize = 22;
|
||||||
@ -49,7 +49,6 @@ function SelectMapModal({
|
|||||||
removeMap,
|
removeMap,
|
||||||
resetMap,
|
resetMap,
|
||||||
updateMap,
|
updateMap,
|
||||||
updateMapState,
|
|
||||||
} = useContext(MapDataContext);
|
} = useContext(MapDataContext);
|
||||||
|
|
||||||
const [imageLoading, setImageLoading] = useState(false);
|
const [imageLoading, setImageLoading] = useState(false);
|
||||||
@ -62,6 +61,8 @@ function SelectMapModal({
|
|||||||
(state) => state.mapId === selectedMapId
|
(state) => state.mapId === selectedMapId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||||
|
|
||||||
const fileInputRef = useRef();
|
const fileInputRef = useRef();
|
||||||
|
|
||||||
async function handleImagesUpload(files) {
|
async function handleImagesUpload(files) {
|
||||||
@ -175,8 +176,6 @@ function SelectMapModal({
|
|||||||
|
|
||||||
async function handleMapRemove(id) {
|
async function handleMapRemove(id) {
|
||||||
await removeMap(id);
|
await removeMap(id);
|
||||||
setMapSettingChanges({});
|
|
||||||
setMapStateSettingChanges({});
|
|
||||||
setSelectedMapId(null);
|
setSelectedMapId(null);
|
||||||
// Removed the map from the map screen if needed
|
// Removed the map from the map screen if needed
|
||||||
if (currentMap && currentMap.id === selectedMapId) {
|
if (currentMap && currentMap.id === selectedMapId) {
|
||||||
@ -185,7 +184,6 @@ function SelectMapModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapSelect(map) {
|
async function handleMapSelect(map) {
|
||||||
await applyMapChanges();
|
|
||||||
if (map) {
|
if (map) {
|
||||||
setSelectedMapId(map.id);
|
setSelectedMapId(map.id);
|
||||||
} else {
|
} else {
|
||||||
@ -202,9 +200,6 @@ function SelectMapModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleClose() {
|
async function handleClose() {
|
||||||
if (selectedMapId) {
|
|
||||||
await applyMapChanges();
|
|
||||||
}
|
|
||||||
onDone();
|
onDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,74 +208,16 @@ function SelectMapModal({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedMapId) {
|
if (selectedMapId) {
|
||||||
await applyMapChanges();
|
|
||||||
// Update last used for cache invalidation
|
// Update last used for cache invalidation
|
||||||
const lastUsed = Date.now();
|
const lastUsed = Date.now();
|
||||||
await updateMap(selectedMapId, { lastUsed });
|
await updateMap(selectedMapId, { lastUsed });
|
||||||
onMapChange(
|
onMapChange({ ...selectedMap, lastUsed }, selectedMapState);
|
||||||
{ ...selectedMapWithChanges, lastUsed },
|
|
||||||
selectedMapStateWithChanges
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
onMapChange(null, null);
|
onMapChange(null, null);
|
||||||
}
|
}
|
||||||
onDone();
|
onDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Map settings
|
|
||||||
*/
|
|
||||||
const [showMoreSettings, setShowMoreSettings] = useState(false);
|
|
||||||
// Local cache of map setting changes
|
|
||||||
// Applied when done is clicked or map selection is changed
|
|
||||||
const [mapSettingChanges, setMapSettingChanges] = useState({});
|
|
||||||
const [mapStateSettingChanges, setMapStateSettingChanges] = useState({});
|
|
||||||
|
|
||||||
function handleMapSettingsChange(key, value) {
|
|
||||||
setMapSettingChanges((prevChanges) => ({
|
|
||||||
...prevChanges,
|
|
||||||
[key]: value,
|
|
||||||
lastModified: Date.now(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMapStateSettingsChange(key, value) {
|
|
||||||
setMapStateSettingChanges((prevChanges) => ({
|
|
||||||
...prevChanges,
|
|
||||||
[key]: value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function applyMapChanges() {
|
|
||||||
if (
|
|
||||||
selectedMapId &&
|
|
||||||
(!isEmpty(mapSettingChanges) || !isEmpty(mapStateSettingChanges))
|
|
||||||
) {
|
|
||||||
// Ensure grid values are positive
|
|
||||||
let verifiedChanges = { ...mapSettingChanges };
|
|
||||||
if ("gridX" in verifiedChanges) {
|
|
||||||
verifiedChanges.gridX = verifiedChanges.gridX || 1;
|
|
||||||
}
|
|
||||||
if ("gridY" in verifiedChanges) {
|
|
||||||
verifiedChanges.gridY = verifiedChanges.gridY || 1;
|
|
||||||
}
|
|
||||||
await updateMap(selectedMapId, verifiedChanges);
|
|
||||||
await updateMapState(selectedMapId, mapStateSettingChanges);
|
|
||||||
|
|
||||||
setMapSettingChanges({});
|
|
||||||
setMapStateSettingChanges({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedMapWithChanges = selectedMap && {
|
|
||||||
...selectedMap,
|
|
||||||
...mapSettingChanges,
|
|
||||||
};
|
|
||||||
const selectedMapStateWithChanges = selectedMapState && {
|
|
||||||
...selectedMapState,
|
|
||||||
...mapStateSettingChanges,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
@ -307,31 +244,31 @@ function SelectMapModal({
|
|||||||
<MapTiles
|
<MapTiles
|
||||||
maps={ownedMaps}
|
maps={ownedMaps}
|
||||||
onMapAdd={openImageDialog}
|
onMapAdd={openImageDialog}
|
||||||
onMapRemove={handleMapRemove}
|
onMapEdit={() => setIsEditModalOpen(true)}
|
||||||
selectedMap={selectedMapWithChanges}
|
|
||||||
selectedMapState={selectedMapStateWithChanges}
|
|
||||||
onMapSelect={handleMapSelect}
|
|
||||||
onMapReset={handleMapReset}
|
onMapReset={handleMapReset}
|
||||||
|
onMapRemove={handleMapRemove}
|
||||||
|
selectedMap={selectedMap}
|
||||||
|
selectedMapState={selectedMapState}
|
||||||
|
onMapSelect={handleMapSelect}
|
||||||
onDone={handleDone}
|
onDone={handleDone}
|
||||||
/>
|
/>
|
||||||
<MapSettings
|
|
||||||
map={selectedMapWithChanges}
|
|
||||||
mapState={selectedMapStateWithChanges}
|
|
||||||
onSettingsChange={handleMapSettingsChange}
|
|
||||||
onStateSettingsChange={handleMapStateSettingsChange}
|
|
||||||
showMore={showMoreSettings}
|
|
||||||
onShowMoreChange={setShowMoreSettings}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
disabled={imageLoading}
|
disabled={imageLoading || !selectedMapId}
|
||||||
onClick={handleDone}
|
onClick={handleDone}
|
||||||
|
mt={2}
|
||||||
>
|
>
|
||||||
Done
|
Select
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</ImageDrop>
|
</ImageDrop>
|
||||||
{imageLoading && <LoadingOverlay bg="overlay" />}
|
{imageLoading && <LoadingOverlay bg="overlay" />}
|
||||||
|
<EditMapModal
|
||||||
|
isOpen={isEditModalOpen}
|
||||||
|
onDone={() => setIsEditModalOpen(false)}
|
||||||
|
map={selectedMap}
|
||||||
|
mapState={selectedMapState}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user