Added token edit modal, refactored map and token data providers
Refactored image drop code into reusable component as well
This commit is contained in:
@@ -1,32 +1,18 @@
|
||||
import React, { useRef, useState, useEffect, useContext } from "react";
|
||||
import { Box, Button, Flex, Label, Text } from "theme-ui";
|
||||
import React, { useRef, useState, useContext } from "react";
|
||||
import { Button, Flex, Label } from "theme-ui";
|
||||
import shortid from "shortid";
|
||||
|
||||
import Modal from "../components/Modal";
|
||||
import MapTiles from "../components/map/MapTiles";
|
||||
import MapSettings from "../components/map/MapSettings";
|
||||
import ImageDrop from "../components/ImageDrop";
|
||||
|
||||
import AuthContext from "../contexts/AuthContext";
|
||||
import DatabaseContext from "../contexts/DatabaseContext";
|
||||
|
||||
import usePrevious from "../helpers/usePrevious";
|
||||
import blobToBuffer from "../helpers/blobToBuffer";
|
||||
|
||||
import { maps as defaultMaps } from "../maps";
|
||||
import MapDataContext from "../contexts/MapDataContext";
|
||||
import AuthContext from "../contexts/AuthContext";
|
||||
|
||||
const defaultMapSize = 22;
|
||||
const defaultMapState = {
|
||||
tokens: {},
|
||||
// An index into the draw actions array to which only actions before the
|
||||
// index will be performed (used in undo and redo)
|
||||
mapDrawActionIndex: -1,
|
||||
mapDrawActions: [],
|
||||
fogDrawActionIndex: -1,
|
||||
fogDrawActions: [],
|
||||
// Flags to determine what other people can edit
|
||||
editFlags: ["drawing", "tokens"],
|
||||
};
|
||||
|
||||
const defaultMapProps = {
|
||||
// Grid type
|
||||
// TODO: add support for hex horizontal and hex vertical
|
||||
@@ -42,68 +28,26 @@ function SelectMapModal({
|
||||
// The map currently being view in the map screen
|
||||
currentMap,
|
||||
}) {
|
||||
const { database } = useContext(DatabaseContext);
|
||||
const { userId } = useContext(AuthContext);
|
||||
|
||||
const wasOpen = usePrevious(isOpen);
|
||||
const {
|
||||
maps,
|
||||
mapStates,
|
||||
addMap,
|
||||
removeMap,
|
||||
resetMap,
|
||||
updateMap,
|
||||
updateMapState,
|
||||
} = useContext(MapDataContext);
|
||||
|
||||
const [imageLoading, setImageLoading] = useState(false);
|
||||
|
||||
// The map selected in the modal
|
||||
const [selectedMap, setSelectedMap] = useState(null);
|
||||
const [selectedMapState, setSelectedMapState] = useState(null);
|
||||
const [maps, setMaps] = useState([]);
|
||||
// Load maps from the database and ensure state is properly setup
|
||||
useEffect(() => {
|
||||
if (!userId || !database) {
|
||||
return;
|
||||
}
|
||||
async function getDefaultMaps() {
|
||||
const defaultMapsWithIds = [];
|
||||
for (let i = 0; i < defaultMaps.length; i++) {
|
||||
const defaultMap = defaultMaps[i];
|
||||
const id = `__default-${defaultMap.name}`;
|
||||
defaultMapsWithIds.push({
|
||||
...defaultMap,
|
||||
id,
|
||||
owner: userId,
|
||||
// Emulate the time increasing to avoid sort errors
|
||||
created: Date.now() + i,
|
||||
lastModified: Date.now() + i,
|
||||
...defaultMapProps,
|
||||
});
|
||||
// Add a state for the map if there isn't one already
|
||||
const state = await database.table("states").get(id);
|
||||
if (!state) {
|
||||
await database.table("states").add({ ...defaultMapState, mapId: id });
|
||||
}
|
||||
}
|
||||
return defaultMapsWithIds;
|
||||
}
|
||||
const [selectedMapId, setSelectedMapId] = useState(null);
|
||||
|
||||
async function loadMaps() {
|
||||
let storedMaps = await database
|
||||
.table("maps")
|
||||
.where({ owner: userId })
|
||||
.toArray();
|
||||
const sortedMaps = storedMaps.sort((a, b) => b.created - a.created);
|
||||
const defaultMapsWithIds = await getDefaultMaps();
|
||||
const allMaps = [...sortedMaps, ...defaultMapsWithIds];
|
||||
setMaps(allMaps);
|
||||
|
||||
// reload map state as is may have changed while the modal was closed
|
||||
if (selectedMap) {
|
||||
const state = await database.table("states").get(selectedMap.id);
|
||||
if (state) {
|
||||
setSelectedMapState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasOpen && isOpen) {
|
||||
loadMaps();
|
||||
}
|
||||
}, [userId, database, isOpen, wasOpen, selectedMap]);
|
||||
const selectedMap = maps.find((map) => map.id === selectedMapId);
|
||||
const selectedMapState = mapStates.find(
|
||||
(state) => state.mapId === selectedMapId
|
||||
);
|
||||
|
||||
const fileInputRef = useRef();
|
||||
|
||||
@@ -180,108 +124,55 @@ function SelectMapModal({
|
||||
}
|
||||
|
||||
async function handleMapAdd(map) {
|
||||
await database.table("maps").add(map);
|
||||
const state = { ...defaultMapState, mapId: map.id };
|
||||
await database.table("states").add(state);
|
||||
setMaps((prevMaps) => [map, ...prevMaps]);
|
||||
setSelectedMap(map);
|
||||
setSelectedMapState(state);
|
||||
await addMap(map);
|
||||
setSelectedMapId(map.id);
|
||||
}
|
||||
|
||||
async function handleMapRemove(id) {
|
||||
await database.table("maps").delete(id);
|
||||
await database.table("states").delete(id);
|
||||
setMaps((prevMaps) => {
|
||||
const filtered = prevMaps.filter((map) => map.id !== id);
|
||||
setSelectedMap(filtered[0]);
|
||||
database.table("states").get(filtered[0].id).then(setSelectedMapState);
|
||||
return filtered;
|
||||
});
|
||||
await removeMap(id);
|
||||
setSelectedMapId(null);
|
||||
// Removed the map from the map screen if needed
|
||||
if (currentMap && currentMap.id === selectedMap.id) {
|
||||
if (currentMap && currentMap.id === selectedMapId) {
|
||||
onMapChange(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMapSelect(map) {
|
||||
const state = await database.table("states").get(map.id);
|
||||
setSelectedMapState(state);
|
||||
setSelectedMap(map);
|
||||
function handleMapSelect(map) {
|
||||
setSelectedMapId(map.id);
|
||||
}
|
||||
|
||||
async function handleMapReset(id) {
|
||||
const state = { ...defaultMapState, mapId: id };
|
||||
await database.table("states").put(state);
|
||||
setSelectedMapState(state);
|
||||
const newState = await resetMap(id);
|
||||
// Reset the state of the current map if needed
|
||||
if (currentMap && currentMap.id === selectedMap.id) {
|
||||
onMapStateChange(state);
|
||||
if (currentMap && currentMap.id === selectedMapId) {
|
||||
onMapStateChange(newState);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
if (selectedMap) {
|
||||
async function handleDone() {
|
||||
if (selectedMapId) {
|
||||
onMapChange(selectedMap, selectedMapState);
|
||||
onDone();
|
||||
}
|
||||
onDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag and Drop
|
||||
*/
|
||||
const [dragging, setDragging] = useState(false);
|
||||
function handleImageDragEnter(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setDragging(true);
|
||||
}
|
||||
|
||||
function handleImageDragLeave(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setDragging(false);
|
||||
}
|
||||
|
||||
function handleImageDrop(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const file = event.dataTransfer.files[0];
|
||||
if (file && file.type.startsWith("image")) {
|
||||
handleImageUpload(file);
|
||||
}
|
||||
setDragging(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map settings
|
||||
*/
|
||||
const [showMoreSettings, setShowMoreSettings] = useState(false);
|
||||
|
||||
async function handleMapSettingsChange(key, value) {
|
||||
const change = { [key]: value, lastModified: Date.now() };
|
||||
database.table("maps").update(selectedMap.id, change);
|
||||
const newMap = { ...selectedMap, ...change };
|
||||
setMaps((prevMaps) => {
|
||||
const newMaps = [...prevMaps];
|
||||
const i = newMaps.findIndex((map) => map.id === selectedMap.id);
|
||||
if (i > -1) {
|
||||
newMaps[i] = newMap;
|
||||
}
|
||||
return newMaps;
|
||||
});
|
||||
setSelectedMap(newMap);
|
||||
await updateMap(selectedMapId, { [key]: value });
|
||||
}
|
||||
|
||||
async function handleMapStateSettingsChange(key, value) {
|
||||
database.table("states").update(selectedMap.id, { [key]: value });
|
||||
setSelectedMapState((prevState) => ({ ...prevState, [key]: value }));
|
||||
await updateMapState(selectedMapId, { [key]: value });
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onRequestClose={onRequestClose}>
|
||||
<Box as="form" onSubmit={handleSubmit} onDragEnter={handleImageDragEnter}>
|
||||
<ImageDrop onDrop={handleImageUpload} dropText="Drop map to upload">
|
||||
<input
|
||||
onChange={(event) => handleImageUpload(event.target.files[0])}
|
||||
type="file"
|
||||
@@ -305,7 +196,7 @@ function SelectMapModal({
|
||||
selectedMapState={selectedMapState}
|
||||
onMapSelect={handleMapSelect}
|
||||
onMapReset={handleMapReset}
|
||||
onSubmit={handleSubmit}
|
||||
onDone={handleDone}
|
||||
/>
|
||||
<MapSettings
|
||||
map={selectedMap}
|
||||
@@ -315,35 +206,15 @@ function SelectMapModal({
|
||||
showMore={showMoreSettings}
|
||||
onShowMoreChange={setShowMoreSettings}
|
||||
/>
|
||||
<Button variant="primary" disabled={imageLoading}>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={imageLoading}
|
||||
onClick={handleDone}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
{dragging && (
|
||||
<Flex
|
||||
bg="muted"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
cursor: "copy",
|
||||
}}
|
||||
onDragLeave={handleImageDragLeave}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
}}
|
||||
onDrop={handleImageDrop}
|
||||
>
|
||||
<Text sx={{ pointerEvents: "none" }}>Drop map to upload</Text>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
</ImageDrop>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user