Added an indexedb database to store uploaded maps into

This commit is contained in:
Mitchell McCaffrey 2020-04-23 15:02:03 +10:00
parent 8681864ddc
commit 071cd3ea7f
9 changed files with 159 additions and 62 deletions

View File

@ -9,6 +9,7 @@
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"blob-to-buffer": "^1.2.8",
"dexie": "^2.0.4",
"gh-pages": "^2.2.0",
"interactjs": "^1.9.7",
"js-binarypack": "^0.0.9",

View File

@ -14,7 +14,9 @@ function AddMapButton({ onMapChange }) {
}
function handleDone(map) {
onMapChange(map);
if (map) {
onMapChange(map);
}
closeModal();
}

View File

@ -1,9 +1,10 @@
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 RemoveIcon from "../../icons/RemoveIcon";
function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) {
function MapSelect({ maps, selectedMap, onMapSelect, onMapAdd, onMapRemove }) {
const tileProps = {
m: 2,
bg: "muted",
@ -18,24 +19,38 @@ function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) {
cursor: "pointer",
};
// TODO move from passing index in to using DB ID
function tile(map, index) {
function tile(map) {
return (
<Flex // TODO: use DB key
key={map.source}
<Flex
key={map.id}
sx={{
borderColor: "primary",
borderStyle: index === selectedMap ? "solid" : "none",
borderStyle: map.id === selectedMap ? "solid" : "none",
borderWidth: "4px",
position: "relative",
...tileStyle,
}}
{...tileProps}
onClick={() => onMapSelected(index)}
onClick={() => onMapSelect(map)}
>
<UIImage
sx={{ width: "100%", objectFit: "contain" }}
sx={{ width: "100%", height: "100%", objectFit: "contain" }}
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>
);
}
@ -54,7 +69,6 @@ function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) {
flexGrow: 1,
}}
>
{maps.map((map, index) => tile(map, index))}
<Flex
onClick={onMapAdd}
sx={{
@ -70,9 +84,12 @@ function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) {
...tileStyle,
}}
{...tileProps}
aria-label="Add Map"
title="Add Map"
>
<AddIcon />
</Flex>
{maps.map(tile)}
</Flex>
);
}

6
src/database.js Normal file
View 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
View 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;

View File

@ -10,40 +10,41 @@ const defaultProps = {
gridY: 22,
width: 1024,
height: 1024,
default: true,
};
export const blank = {
...defaultProps,
source: blankImage,
name: "Blank Grid 22x22",
id: "Blank Grid 22x22",
};
export const grass = {
...defaultProps,
source: grassImage,
name: "Grass Grid 22x22",
id: "Grass Grid 22x22",
};
export const sand = {
...defaultProps,
source: sandImage,
name: "Sand Grid 22x22",
id: "Sand Grid 22x22",
};
export const stone = {
...defaultProps,
source: stoneImage,
name: "Stone Grid 22x22",
id: "Stone Grid 22x22",
};
export const water = {
...defaultProps,
source: waterImage,
name: "Water Grid 22x22",
id: "Water Grid 22x22",
};
export const wood = {
...defaultProps,
source: woodImage,
name: "Wood Grid 22x22",
id: "Wood Grid 22x22",
};

View File

@ -1,5 +1,8 @@
import React, { useRef, useState, useEffect } from "react";
import { Box, Button, Flex, Label, Input, Text } from "theme-ui";
import shortid from "shortid";
import db from "../database";
import Modal from "../components/Modal";
import MapSelect from "../components/map/MapSelect";
@ -11,23 +14,25 @@ const defaultMapSize = 22;
function AddMapModal({ isOpen, onRequestClose, onDone }) {
const [imageLoading, setImageLoading] = useState(false);
const [currentMap, setCurrentMap] = useState(-1);
const [currentMapId, setCurrentMapId] = useState(null);
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 [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();
function handleImageUpload(file) {
@ -54,23 +59,15 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
let image = new Image();
setImageLoading(true);
image.onload = function () {
setMaps((prevMaps) => {
const newMaps = [
...prevMaps,
{
file,
gridX: fileGridX,
gridY: fileGridY,
width: image.width,
height: image.height,
source: url,
},
];
setCurrentMap(newMaps.length - 1);
return newMaps;
handleMapAdd({
file,
gridX: fileGridX,
gridY: fileGridY,
width: image.width,
height: image.height,
source: url,
id: shortid.generate(),
});
setGridX(fileGridX);
setGridY(fileGridY);
setImageLoading(false);
};
image.src = url;
@ -82,10 +79,60 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
}
}
function handleMapSelect(mapId) {
setCurrentMap(mapId);
setGridX(maps[mapId].gridX);
setGridY(maps[mapId].gridY);
async function handleMapAdd(map) {
await db.table("maps").add(map);
setMaps((prevMaps) => [map, ...prevMaps]);
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 (
<Modal isOpen={isOpen} onRequestClose={onRequestClose}>
<Box
as="form"
onSubmit={(e) => {
e.preventDefault();
onDone(maps[currentMap]);
}}
onDragEnter={handleImageDragEnter}
>
<Box as="form" onSubmit={handleSubmit} onDragEnter={handleImageDragEnter}>
<input
onChange={(event) => handleImageUpload(event.target.files[0])}
type="file"
@ -142,8 +182,9 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
<MapSelect
maps={maps}
onMapAdd={openImageDialog}
selectedMap={currentMap}
onMapSelected={handleMapSelect}
onMapRemove={handleMapRemove}
selectedMap={currentMapId}
onMapSelect={handleMapSelect}
/>
<Flex>
<Box mb={2} mr={1} sx={{ flexGrow: 1 }}>
@ -152,7 +193,9 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
type="number"
name="gridX"
value={gridX}
onChange={(e) => setGridX(e.target.value)}
onChange={handleGridXChange}
disabled={currentMapId === null}
min={1}
/>
</Box>
<Box mb={2} ml={1} sx={{ flexGrow: 1 }}>
@ -161,7 +204,9 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
type="number"
name="gridY"
value={gridY}
onChange={(e) => setGridY(e.target.value)}
onChange={handleGridYChange}
disabled={currentMapId === null}
min={1}
/>
</Box>
</Flex>

View File

@ -180,6 +180,8 @@ export default {
},
"&:disabled": {
backgroundColor: "muted",
color: "gray",
borderColor: "text",
},
},
},

View File

@ -4005,6 +4005,11 @@ detect-port-alt@1.1.6:
address "^1.0.1"
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:
version "24.9.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"