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>
|
||||
</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 && (
|
||||
<>
|
||||
<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
|
||||
mt={2}
|
||||
mb={mapEmpty || map.type === "default" ? 2 : 0}
|
||||
|
@ -1,23 +1,12 @@
|
||||
import React, { useState } from "react";
|
||||
import { Flex, Image as UIImage, IconButton, Box, Text } from "theme-ui";
|
||||
|
||||
import RemoveMapIcon from "../../icons/RemoveMapIcon";
|
||||
import ResetMapIcon from "../../icons/ResetMapIcon";
|
||||
import ExpandMoreDotIcon from "../../icons/ExpandMoreDotIcon";
|
||||
import EditMapIcon from "../../icons/EditMapIcon";
|
||||
|
||||
import useDataSource from "../../helpers/useDataSource";
|
||||
import { mapSources as defaultMapSources, unknownSource } from "../../maps";
|
||||
|
||||
function MapTile({
|
||||
map,
|
||||
mapState,
|
||||
isSelected,
|
||||
onMapSelect,
|
||||
onMapRemove,
|
||||
onMapReset,
|
||||
onDone,
|
||||
large,
|
||||
}) {
|
||||
function MapTile({ map, isSelected, onMapSelect, onMapEdit, onDone, large }) {
|
||||
const [isMapTileMenuOpen, setIsTileMenuOpen] = useState(false);
|
||||
const isDefault = map.type === "default";
|
||||
const mapSource = useDataSource(
|
||||
@ -30,69 +19,6 @@ function MapTile({
|
||||
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 (
|
||||
<Flex
|
||||
key={map.id}
|
||||
@ -174,30 +100,22 @@ function MapTile({
|
||||
{/* Show expand button only if both reset and remove is available */}
|
||||
{isSelected && (
|
||||
<Box sx={{ position: "absolute", top: 0, right: 0 }}>
|
||||
{isDefault && hasMapState && resetButton(map)}
|
||||
{!isDefault && hasMapState && !isMapTileMenuOpen && expandButton}
|
||||
{!isDefault && !hasMapState && removeButton(map)}
|
||||
<IconButton
|
||||
aria-label="Edit Map"
|
||||
title="Edit Map"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onMapEdit(map.id);
|
||||
}}
|
||||
bg="overlay"
|
||||
sx={{ borderRadius: "50%" }}
|
||||
m={2}
|
||||
>
|
||||
<EditMapIcon />
|
||||
</IconButton>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
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 { useMedia } from "react-media";
|
||||
|
||||
import AddIcon from "../../icons/AddIcon";
|
||||
import RemoveMapIcon from "../../icons/RemoveMapIcon";
|
||||
import ResetMapIcon from "../../icons/ResetMapIcon";
|
||||
|
||||
import MapTile from "./MapTile";
|
||||
import Link from "../Link";
|
||||
@ -15,19 +17,27 @@ function MapTiles({
|
||||
selectedMap,
|
||||
selectedMapState,
|
||||
onMapSelect,
|
||||
onMapAdd,
|
||||
onMapRemove,
|
||||
onMapReset,
|
||||
onMapAdd,
|
||||
onMapEdit,
|
||||
onDone,
|
||||
}) {
|
||||
const { databaseStatus } = useContext(DatabaseContext);
|
||||
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 (
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<SimpleBar style={{ maxHeight: "300px" }}>
|
||||
<SimpleBar style={{ maxHeight: "400px" }}>
|
||||
<Flex
|
||||
p={2}
|
||||
pb={4}
|
||||
bg="muted"
|
||||
sx={{
|
||||
flexWrap: "wrap",
|
||||
@ -79,14 +89,10 @@ function MapTiles({
|
||||
return (
|
||||
<MapTile
|
||||
key={map.id}
|
||||
// TODO: Move to selected map here and fix url error
|
||||
// when done is clicked
|
||||
map={map}
|
||||
mapState={isSelected && selectedMapState}
|
||||
isSelected={isSelected}
|
||||
onMapSelect={onMapSelect}
|
||||
onMapRemove={onMapRemove}
|
||||
onMapReset={onMapReset}
|
||||
onMapEdit={onMapEdit}
|
||||
onDone={onDone}
|
||||
large={isSmallScreen}
|
||||
/>
|
||||
@ -112,6 +118,41 @@ function MapTiles({
|
||||
</Text>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
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 shortid from "shortid";
|
||||
|
||||
import EditMapModal from "./EditMapModal";
|
||||
|
||||
import Modal from "../components/Modal";
|
||||
import MapTiles from "../components/map/MapTiles";
|
||||
import MapSettings from "../components/map/MapSettings";
|
||||
import ImageDrop from "../components/ImageDrop";
|
||||
import LoadingOverlay from "../components/LoadingOverlay";
|
||||
|
||||
@ -13,7 +14,6 @@ import blobToBuffer from "../helpers/blobToBuffer";
|
||||
import MapDataContext from "../contexts/MapDataContext";
|
||||
import AuthContext from "../contexts/AuthContext";
|
||||
|
||||
import { isEmpty } from "../helpers/shared";
|
||||
import { resizeImage } from "../helpers/image";
|
||||
|
||||
const defaultMapSize = 22;
|
||||
@ -49,7 +49,6 @@ function SelectMapModal({
|
||||
removeMap,
|
||||
resetMap,
|
||||
updateMap,
|
||||
updateMapState,
|
||||
} = useContext(MapDataContext);
|
||||
|
||||
const [imageLoading, setImageLoading] = useState(false);
|
||||
@ -62,6 +61,8 @@ function SelectMapModal({
|
||||
(state) => state.mapId === selectedMapId
|
||||
);
|
||||
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
|
||||
const fileInputRef = useRef();
|
||||
|
||||
async function handleImagesUpload(files) {
|
||||
@ -175,8 +176,6 @@ function SelectMapModal({
|
||||
|
||||
async function handleMapRemove(id) {
|
||||
await removeMap(id);
|
||||
setMapSettingChanges({});
|
||||
setMapStateSettingChanges({});
|
||||
setSelectedMapId(null);
|
||||
// Removed the map from the map screen if needed
|
||||
if (currentMap && currentMap.id === selectedMapId) {
|
||||
@ -185,7 +184,6 @@ function SelectMapModal({
|
||||
}
|
||||
|
||||
async function handleMapSelect(map) {
|
||||
await applyMapChanges();
|
||||
if (map) {
|
||||
setSelectedMapId(map.id);
|
||||
} else {
|
||||
@ -202,9 +200,6 @@ function SelectMapModal({
|
||||
}
|
||||
|
||||
async function handleClose() {
|
||||
if (selectedMapId) {
|
||||
await applyMapChanges();
|
||||
}
|
||||
onDone();
|
||||
}
|
||||
|
||||
@ -213,74 +208,16 @@ function SelectMapModal({
|
||||
return;
|
||||
}
|
||||
if (selectedMapId) {
|
||||
await applyMapChanges();
|
||||
// Update last used for cache invalidation
|
||||
const lastUsed = Date.now();
|
||||
await updateMap(selectedMapId, { lastUsed });
|
||||
onMapChange(
|
||||
{ ...selectedMapWithChanges, lastUsed },
|
||||
selectedMapStateWithChanges
|
||||
);
|
||||
onMapChange({ ...selectedMap, lastUsed }, selectedMapState);
|
||||
} else {
|
||||
onMapChange(null, null);
|
||||
}
|
||||
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 (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
@ -307,31 +244,31 @@ function SelectMapModal({
|
||||
<MapTiles
|
||||
maps={ownedMaps}
|
||||
onMapAdd={openImageDialog}
|
||||
onMapRemove={handleMapRemove}
|
||||
selectedMap={selectedMapWithChanges}
|
||||
selectedMapState={selectedMapStateWithChanges}
|
||||
onMapSelect={handleMapSelect}
|
||||
onMapEdit={() => setIsEditModalOpen(true)}
|
||||
onMapReset={handleMapReset}
|
||||
onMapRemove={handleMapRemove}
|
||||
selectedMap={selectedMap}
|
||||
selectedMapState={selectedMapState}
|
||||
onMapSelect={handleMapSelect}
|
||||
onDone={handleDone}
|
||||
/>
|
||||
<MapSettings
|
||||
map={selectedMapWithChanges}
|
||||
mapState={selectedMapStateWithChanges}
|
||||
onSettingsChange={handleMapSettingsChange}
|
||||
onStateSettingsChange={handleMapStateSettingsChange}
|
||||
showMore={showMoreSettings}
|
||||
onShowMoreChange={setShowMoreSettings}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={imageLoading}
|
||||
disabled={imageLoading || !selectedMapId}
|
||||
onClick={handleDone}
|
||||
mt={2}
|
||||
>
|
||||
Done
|
||||
Select
|
||||
</Button>
|
||||
</Flex>
|
||||
</ImageDrop>
|
||||
{imageLoading && <LoadingOverlay bg="overlay" />}
|
||||
<EditMapModal
|
||||
isOpen={isEditModalOpen}
|
||||
onDone={() => setIsEditModalOpen(false)}
|
||||
map={selectedMap}
|
||||
mapState={selectedMapState}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user