Added an indexedb database to store uploaded maps into
This commit is contained in:
parent
8681864ddc
commit
071cd3ea7f
@ -9,6 +9,7 @@
|
|||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
"blob-to-buffer": "^1.2.8",
|
"blob-to-buffer": "^1.2.8",
|
||||||
|
"dexie": "^2.0.4",
|
||||||
"gh-pages": "^2.2.0",
|
"gh-pages": "^2.2.0",
|
||||||
"interactjs": "^1.9.7",
|
"interactjs": "^1.9.7",
|
||||||
"js-binarypack": "^0.0.9",
|
"js-binarypack": "^0.0.9",
|
||||||
|
@ -14,7 +14,9 @@ function AddMapButton({ onMapChange }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDone(map) {
|
function handleDone(map) {
|
||||||
onMapChange(map);
|
if (map) {
|
||||||
|
onMapChange(map);
|
||||||
|
}
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Flex, Image as UIImage } from "theme-ui";
|
import { Flex, Image as UIImage, IconButton } from "theme-ui";
|
||||||
|
|
||||||
import AddIcon from "../../icons/AddIcon";
|
import AddIcon from "../../icons/AddIcon";
|
||||||
|
import RemoveIcon from "../../icons/RemoveIcon";
|
||||||
|
|
||||||
function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) {
|
function MapSelect({ maps, selectedMap, onMapSelect, onMapAdd, onMapRemove }) {
|
||||||
const tileProps = {
|
const tileProps = {
|
||||||
m: 2,
|
m: 2,
|
||||||
bg: "muted",
|
bg: "muted",
|
||||||
@ -18,24 +19,38 @@ function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) {
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO move from passing index in to using DB ID
|
function tile(map) {
|
||||||
function tile(map, index) {
|
|
||||||
return (
|
return (
|
||||||
<Flex // TODO: use DB key
|
<Flex
|
||||||
key={map.source}
|
key={map.id}
|
||||||
sx={{
|
sx={{
|
||||||
borderColor: "primary",
|
borderColor: "primary",
|
||||||
borderStyle: index === selectedMap ? "solid" : "none",
|
borderStyle: map.id === selectedMap ? "solid" : "none",
|
||||||
borderWidth: "4px",
|
borderWidth: "4px",
|
||||||
|
position: "relative",
|
||||||
...tileStyle,
|
...tileStyle,
|
||||||
}}
|
}}
|
||||||
{...tileProps}
|
{...tileProps}
|
||||||
onClick={() => onMapSelected(index)}
|
onClick={() => onMapSelect(map)}
|
||||||
>
|
>
|
||||||
<UIImage
|
<UIImage
|
||||||
sx={{ width: "100%", objectFit: "contain" }}
|
sx={{ width: "100%", height: "100%", objectFit: "contain" }}
|
||||||
src={map.source}
|
src={map.source}
|
||||||
/>
|
/>
|
||||||
|
{!map.default && map.id === selectedMap && (
|
||||||
|
<IconButton
|
||||||
|
aria-label="Remove Map"
|
||||||
|
title="Remove map"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onMapRemove(map.id);
|
||||||
|
}}
|
||||||
|
sx={{ position: "absolute", top: 0, right: 0 }}
|
||||||
|
>
|
||||||
|
<RemoveIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -54,7 +69,6 @@ function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) {
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{maps.map((map, index) => tile(map, index))}
|
|
||||||
<Flex
|
<Flex
|
||||||
onClick={onMapAdd}
|
onClick={onMapAdd}
|
||||||
sx={{
|
sx={{
|
||||||
@ -70,9 +84,12 @@ function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) {
|
|||||||
...tileStyle,
|
...tileStyle,
|
||||||
}}
|
}}
|
||||||
{...tileProps}
|
{...tileProps}
|
||||||
|
aria-label="Add Map"
|
||||||
|
title="Add Map"
|
||||||
>
|
>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
{maps.map(tile)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
6
src/database.js
Normal file
6
src/database.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Dexie from "dexie";
|
||||||
|
|
||||||
|
const db = new Dexie("OwlbearRodeoDB");
|
||||||
|
db.version(1).stores({ maps: "id" });
|
||||||
|
|
||||||
|
export default db;
|
18
src/icons/RemoveIcon.js
Normal file
18
src/icons/RemoveIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function RemoveIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<path d="M18.3 5.71c-.39-.39-1.02-.39-1.41 0L12 10.59 7.11 5.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L10.59 12 5.7 16.89c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 13.41l4.89 4.89c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RemoveIcon;
|
@ -10,40 +10,41 @@ const defaultProps = {
|
|||||||
gridY: 22,
|
gridY: 22,
|
||||||
width: 1024,
|
width: 1024,
|
||||||
height: 1024,
|
height: 1024,
|
||||||
|
default: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const blank = {
|
export const blank = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
source: blankImage,
|
source: blankImage,
|
||||||
name: "Blank Grid 22x22",
|
id: "Blank Grid 22x22",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const grass = {
|
export const grass = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
source: grassImage,
|
source: grassImage,
|
||||||
name: "Grass Grid 22x22",
|
id: "Grass Grid 22x22",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sand = {
|
export const sand = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
source: sandImage,
|
source: sandImage,
|
||||||
name: "Sand Grid 22x22",
|
id: "Sand Grid 22x22",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stone = {
|
export const stone = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
source: stoneImage,
|
source: stoneImage,
|
||||||
name: "Stone Grid 22x22",
|
id: "Stone Grid 22x22",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const water = {
|
export const water = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
source: waterImage,
|
source: waterImage,
|
||||||
name: "Water Grid 22x22",
|
id: "Water Grid 22x22",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const wood = {
|
export const wood = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
source: woodImage,
|
source: woodImage,
|
||||||
name: "Wood Grid 22x22",
|
id: "Wood Grid 22x22",
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import React, { useRef, useState, useEffect } from "react";
|
import React, { useRef, useState, useEffect } from "react";
|
||||||
import { Box, Button, Flex, Label, Input, Text } from "theme-ui";
|
import { Box, Button, Flex, Label, Input, Text } from "theme-ui";
|
||||||
|
import shortid from "shortid";
|
||||||
|
|
||||||
|
import db from "../database";
|
||||||
|
|
||||||
import Modal from "../components/Modal";
|
import Modal from "../components/Modal";
|
||||||
import MapSelect from "../components/map/MapSelect";
|
import MapSelect from "../components/map/MapSelect";
|
||||||
@ -11,23 +14,25 @@ const defaultMapSize = 22;
|
|||||||
function AddMapModal({ isOpen, onRequestClose, onDone }) {
|
function AddMapModal({ isOpen, onRequestClose, onDone }) {
|
||||||
const [imageLoading, setImageLoading] = useState(false);
|
const [imageLoading, setImageLoading] = useState(false);
|
||||||
|
|
||||||
const [currentMap, setCurrentMap] = useState(-1);
|
const [currentMapId, setCurrentMapId] = useState(null);
|
||||||
const [maps, setMaps] = useState(Object.values(defaultMaps));
|
const [maps, setMaps] = useState(Object.values(defaultMaps));
|
||||||
|
// Load maps from the database
|
||||||
|
useEffect(() => {
|
||||||
|
async function loadMaps() {
|
||||||
|
let storedMaps = await db.table("maps").toArray();
|
||||||
|
// reverse so maps are show in the order they were added
|
||||||
|
storedMaps.reverse();
|
||||||
|
for (let map of storedMaps) {
|
||||||
|
// Recreate image urls for each map
|
||||||
|
map.source = URL.createObjectURL(map.file);
|
||||||
|
}
|
||||||
|
setMaps((prevMaps) => [...storedMaps, ...prevMaps]);
|
||||||
|
}
|
||||||
|
loadMaps();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [gridX, setGridX] = useState(defaultMapSize);
|
const [gridX, setGridX] = useState(defaultMapSize);
|
||||||
const [gridY, setGridY] = useState(defaultMapSize);
|
const [gridY, setGridY] = useState(defaultMapSize);
|
||||||
useEffect(() => {
|
|
||||||
setMaps((prevMaps) => {
|
|
||||||
const newMaps = [...prevMaps];
|
|
||||||
const changedMap = newMaps[currentMap];
|
|
||||||
if (changedMap) {
|
|
||||||
changedMap.gridX = gridX;
|
|
||||||
changedMap.gridY = gridY;
|
|
||||||
}
|
|
||||||
return newMaps;
|
|
||||||
});
|
|
||||||
}, [gridX, gridY, currentMap]);
|
|
||||||
|
|
||||||
const fileInputRef = useRef();
|
const fileInputRef = useRef();
|
||||||
|
|
||||||
function handleImageUpload(file) {
|
function handleImageUpload(file) {
|
||||||
@ -54,23 +59,15 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
|
|||||||
let image = new Image();
|
let image = new Image();
|
||||||
setImageLoading(true);
|
setImageLoading(true);
|
||||||
image.onload = function () {
|
image.onload = function () {
|
||||||
setMaps((prevMaps) => {
|
handleMapAdd({
|
||||||
const newMaps = [
|
file,
|
||||||
...prevMaps,
|
gridX: fileGridX,
|
||||||
{
|
gridY: fileGridY,
|
||||||
file,
|
width: image.width,
|
||||||
gridX: fileGridX,
|
height: image.height,
|
||||||
gridY: fileGridY,
|
source: url,
|
||||||
width: image.width,
|
id: shortid.generate(),
|
||||||
height: image.height,
|
|
||||||
source: url,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
setCurrentMap(newMaps.length - 1);
|
|
||||||
return newMaps;
|
|
||||||
});
|
});
|
||||||
setGridX(fileGridX);
|
|
||||||
setGridY(fileGridY);
|
|
||||||
setImageLoading(false);
|
setImageLoading(false);
|
||||||
};
|
};
|
||||||
image.src = url;
|
image.src = url;
|
||||||
@ -82,10 +79,60 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMapSelect(mapId) {
|
async function handleMapAdd(map) {
|
||||||
setCurrentMap(mapId);
|
await db.table("maps").add(map);
|
||||||
setGridX(maps[mapId].gridX);
|
setMaps((prevMaps) => [map, ...prevMaps]);
|
||||||
setGridY(maps[mapId].gridY);
|
setCurrentMapId(map.id);
|
||||||
|
setGridX(map.gridX);
|
||||||
|
setGridY(map.gridY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMapRemove(id) {
|
||||||
|
await db.table("maps").delete(id);
|
||||||
|
setMaps((prevMaps) => {
|
||||||
|
const filtered = prevMaps.filter((map) => map.id !== id);
|
||||||
|
setCurrentMapId(filtered[0].id);
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMapSelect(map) {
|
||||||
|
setCurrentMapId(map.id);
|
||||||
|
setGridX(map.gridX);
|
||||||
|
setGridY(map.gridY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
onDone(maps.find((map) => map.id === currentMapId));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGridXChange(e) {
|
||||||
|
const newX = e.target.value;
|
||||||
|
await db.table("maps").update(currentMapId, { gridX: newX });
|
||||||
|
setGridX(newX);
|
||||||
|
setMaps((prevMaps) => {
|
||||||
|
const newMaps = [...prevMaps];
|
||||||
|
const i = newMaps.findIndex((map) => map.id === currentMapId);
|
||||||
|
if (i > -1) {
|
||||||
|
newMaps[i].gridX = newX;
|
||||||
|
}
|
||||||
|
return newMaps;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGridYChange(e) {
|
||||||
|
const newY = e.target.value;
|
||||||
|
await db.table("maps").update(currentMapId, { gridY: newY });
|
||||||
|
setGridY(newY);
|
||||||
|
setMaps((prevMaps) => {
|
||||||
|
const newMaps = [...prevMaps];
|
||||||
|
const i = newMaps.findIndex((map) => map.id === currentMapId);
|
||||||
|
if (i > -1) {
|
||||||
|
newMaps[i].gridY = newY;
|
||||||
|
}
|
||||||
|
return newMaps;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,14 +163,7 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onRequestClose={onRequestClose}>
|
<Modal isOpen={isOpen} onRequestClose={onRequestClose}>
|
||||||
<Box
|
<Box as="form" onSubmit={handleSubmit} onDragEnter={handleImageDragEnter}>
|
||||||
as="form"
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
onDone(maps[currentMap]);
|
|
||||||
}}
|
|
||||||
onDragEnter={handleImageDragEnter}
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
onChange={(event) => handleImageUpload(event.target.files[0])}
|
onChange={(event) => handleImageUpload(event.target.files[0])}
|
||||||
type="file"
|
type="file"
|
||||||
@ -142,8 +182,9 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
|
|||||||
<MapSelect
|
<MapSelect
|
||||||
maps={maps}
|
maps={maps}
|
||||||
onMapAdd={openImageDialog}
|
onMapAdd={openImageDialog}
|
||||||
selectedMap={currentMap}
|
onMapRemove={handleMapRemove}
|
||||||
onMapSelected={handleMapSelect}
|
selectedMap={currentMapId}
|
||||||
|
onMapSelect={handleMapSelect}
|
||||||
/>
|
/>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Box mb={2} mr={1} sx={{ flexGrow: 1 }}>
|
<Box mb={2} mr={1} sx={{ flexGrow: 1 }}>
|
||||||
@ -152,7 +193,9 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
|
|||||||
type="number"
|
type="number"
|
||||||
name="gridX"
|
name="gridX"
|
||||||
value={gridX}
|
value={gridX}
|
||||||
onChange={(e) => setGridX(e.target.value)}
|
onChange={handleGridXChange}
|
||||||
|
disabled={currentMapId === null}
|
||||||
|
min={1}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box mb={2} ml={1} sx={{ flexGrow: 1 }}>
|
<Box mb={2} ml={1} sx={{ flexGrow: 1 }}>
|
||||||
@ -161,7 +204,9 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
|
|||||||
type="number"
|
type="number"
|
||||||
name="gridY"
|
name="gridY"
|
||||||
value={gridY}
|
value={gridY}
|
||||||
onChange={(e) => setGridY(e.target.value)}
|
onChange={handleGridYChange}
|
||||||
|
disabled={currentMapId === null}
|
||||||
|
min={1}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -180,6 +180,8 @@ export default {
|
|||||||
},
|
},
|
||||||
"&:disabled": {
|
"&:disabled": {
|
||||||
backgroundColor: "muted",
|
backgroundColor: "muted",
|
||||||
|
color: "gray",
|
||||||
|
borderColor: "text",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4005,6 +4005,11 @@ detect-port-alt@1.1.6:
|
|||||||
address "^1.0.1"
|
address "^1.0.1"
|
||||||
debug "^2.6.0"
|
debug "^2.6.0"
|
||||||
|
|
||||||
|
dexie@^2.0.4:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/dexie/-/dexie-2.0.4.tgz#6027a5e05879424e8f9979d8c14e7420f27e3a11"
|
||||||
|
integrity sha512-aQ/s1U2wHxwBKRrt2Z/mwFNHMQWhESerFsMYzE+5P5OsIe5o1kgpFMWkzKTtkvkyyEni6mWr/T4HUJuY9xIHLA==
|
||||||
|
|
||||||
diff-sequences@^24.9.0:
|
diff-sequences@^24.9.0:
|
||||||
version "24.9.0"
|
version "24.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
|
||||||
|
Loading…
Reference in New Issue
Block a user