diff --git a/package.json b/package.json index 27b9b63..dd0a35d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/map/AddMapButton.js b/src/components/map/AddMapButton.js index 99dd26b..ac95b20 100644 --- a/src/components/map/AddMapButton.js +++ b/src/components/map/AddMapButton.js @@ -14,7 +14,9 @@ function AddMapButton({ onMapChange }) { } function handleDone(map) { - onMapChange(map); + if (map) { + onMapChange(map); + } closeModal(); } diff --git a/src/components/map/MapSelect.js b/src/components/map/MapSelect.js index 40b3e42..817fea8 100644 --- a/src/components/map/MapSelect.js +++ b/src/components/map/MapSelect.js @@ -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 ( - onMapSelected(index)} + onClick={() => onMapSelect(map)} > + {!map.default && map.id === selectedMap && ( + { + e.preventDefault(); + e.stopPropagation(); + onMapRemove(map.id); + }} + sx={{ position: "absolute", top: 0, right: 0 }} + > + + + )} ); } @@ -54,7 +69,6 @@ function MapSelect({ maps, selectedMap, onMapSelected, onMapAdd }) { flexGrow: 1, }} > - {maps.map((map, index) => tile(map, index))} + {maps.map(tile)} ); } diff --git a/src/database.js b/src/database.js new file mode 100644 index 0000000..cf9c91f --- /dev/null +++ b/src/database.js @@ -0,0 +1,6 @@ +import Dexie from "dexie"; + +const db = new Dexie("OwlbearRodeoDB"); +db.version(1).stores({ maps: "id" }); + +export default db; diff --git a/src/icons/RemoveIcon.js b/src/icons/RemoveIcon.js new file mode 100644 index 0000000..d09fbc8 --- /dev/null +++ b/src/icons/RemoveIcon.js @@ -0,0 +1,18 @@ +import React from "react"; + +function RemoveIcon() { + return ( + + + + + ); +} + +export default RemoveIcon; diff --git a/src/maps/index.js b/src/maps/index.js index 5deb7b4..4a9524d 100644 --- a/src/maps/index.js +++ b/src/maps/index.js @@ -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", }; diff --git a/src/modals/AddMapModal.js b/src/modals/AddMapModal.js index 07f742f..1e68ecb 100644 --- a/src/modals/AddMapModal.js +++ b/src/modals/AddMapModal.js @@ -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 ( - { - e.preventDefault(); - onDone(maps[currentMap]); - }} - onDragEnter={handleImageDragEnter} - > + handleImageUpload(event.target.files[0])} type="file" @@ -142,8 +182,9 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) { @@ -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} /> @@ -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} /> diff --git a/src/theme.js b/src/theme.js index 5823fb6..6e28eea 100644 --- a/src/theme.js +++ b/src/theme.js @@ -180,6 +180,8 @@ export default { }, "&:disabled": { backgroundColor: "muted", + color: "gray", + borderColor: "text", }, }, }, diff --git a/yarn.lock b/yarn.lock index 650a864..2d7160e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"