Added state to default maps and added clear state option to map tile

This commit is contained in:
Mitchell McCaffrey 2020-04-23 20:32:33 +10:00
parent 6f0df1c674
commit 10c259a6b3
8 changed files with 250 additions and 80 deletions

View File

@ -0,0 +1,143 @@
import React, { useState, useEffect } from "react";
import { Flex, Image as UIImage, IconButton, Box } from "theme-ui";
import db from "../../database";
import RemoveMapIcon from "../../icons/RemoveMapIcon";
import ResetMapIcon from "../../icons/ResetMapIcon";
import ExpandMoreDotIcon from "../../icons/ExpandMoreDotIcon";
function MapTile({ map, isSelected, onMapSelect, onMapRemove, onMapReset }) {
const [isMapTileMenuOpen, setIsTileMenuOpen] = useState(false);
const [hasMapState, setHasMapState] = useState(false);
useEffect(() => {
async function checkForMapState() {
const state = await db.table("states").get(map.id);
if (
state &&
(Object.values(state.tokens).length > 0 || state.drawActions.length > 0)
) {
setHasMapState(true);
}
}
checkForMapState();
}, [map]);
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={1}
>
<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={1}
>
<RemoveMapIcon />
</IconButton>
);
}
function resetButton(map) {
return (
<IconButton
aria-label="Reset Map"
title="Reset Map"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setHasMapState(false);
setIsTileMenuOpen(false);
onMapReset(map.id);
}}
bg="overlay"
sx={{ borderRadius: "50%" }}
m={1}
>
<ResetMapIcon />
</IconButton>
);
}
return (
<Flex
key={map.id}
sx={{
borderColor: "primary",
borderStyle: isSelected ? "solid" : "none",
borderWidth: "4px",
position: "relative",
width: "150px",
height: "150px",
borderRadius: "4px",
justifyContent: "center",
alignItems: "center",
cursor: "pointer",
}}
m={2}
bg="muted"
onClick={() => {
setIsTileMenuOpen(false);
onMapSelect(map);
}}
>
<UIImage
sx={{ width: "100%", height: "100%", objectFit: "contain" }}
src={map.source}
/>
{/* Show expand button only if both reset and remove is available */}
{isSelected && (
<Box sx={{ position: "absolute", top: 0, right: 0 }}>
{map.default && hasMapState && resetButton(map)}
{!map.default && hasMapState && !isMapTileMenuOpen && expandButton}
{!map.default && !hasMapState && removeButton(map)}
</Box>
)}
{/* Tile menu for two actions */}
{!map.default && isMapTileMenuOpen && isSelected && (
<Flex
sx={{
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
alignItems: "center",
justifyContent: "center",
}}
bg="muted"
onClick={() => setIsTileMenuOpen(false)}
>
{!map.default && removeButton(map)}
{hasMapState && resetButton(map)}
</Flex>
)}
</Flex>
);
}
export default MapTile;

View File

@ -1,60 +1,18 @@
import React from "react";
import { Flex, Image as UIImage, IconButton } from "theme-ui";
import { Flex } from "theme-ui";
import AddIcon from "../../icons/AddIcon";
import RemoveIcon from "../../icons/RemoveIcon";
function MapTiles({ maps, selectedMap, onMapSelect, onMapAdd, onMapRemove }) {
const tileProps = {
m: 2,
bg: "muted",
};
const tileStyle = {
width: "150px",
height: "150px",
borderRadius: "4px",
justifyContent: "center",
alignItems: "center",
cursor: "pointer",
};
function tile(map) {
return (
<Flex
key={map.id}
sx={{
borderColor: "primary",
borderStyle: map.id === selectedMap ? "solid" : "none",
borderWidth: "4px",
position: "relative",
...tileStyle,
}}
{...tileProps}
onClick={() => onMapSelect(map)}
>
<UIImage
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>
);
}
import MapTile from "./MapTile";
function MapTiles({
maps,
selectedMap,
onMapSelect,
onMapAdd,
onMapRemove,
onMapReset,
}) {
return (
<Flex
my={2}
@ -81,15 +39,30 @@ function MapTiles({ maps, selectedMap, onMapSelect, onMapAdd, onMapRemove }) {
":active": {
color: "secondary",
},
...tileStyle,
width: "150px",
height: "150px",
borderRadius: "4px",
justifyContent: "center",
alignItems: "center",
cursor: "pointer",
}}
{...tileProps}
m={2}
bg="muted"
aria-label="Add Map"
title="Add Map"
>
<AddIcon large />
</Flex>
{maps.map(tile)}
{maps.map((map) => (
<MapTile
key={map.id}
map={map}
isSelected={map.id === selectedMap}
onMapSelect={onMapSelect}
onMapRemove={onMapRemove}
onMapReset={onMapReset}
/>
))}
</Flex>
);
}

View File

@ -0,0 +1,18 @@
import React from "react";
function ExpandMoreDotIcon() {
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="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" />
</svg>
);
}
export default ExpandMoreDotIcon;

View File

@ -1,18 +0,0 @@
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

@ -0,0 +1,18 @@
import React from "react";
function RemoveMapIcon() {
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="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v10zM18 4h-2.5l-.71-.71c-.18-.18-.44-.29-.7-.29H9.91c-.26 0-.52.11-.7.29L8.5 4H6c-.55 0-1 .45-1 1s.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1z" />
</svg>
);
}
export default RemoveMapIcon;

18
src/icons/ResetMapIcon.js Normal file
View File

@ -0,0 +1,18 @@
import React from "react";
function ResetMapIcon() {
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="M17.65 6.35c-1.63-1.63-3.94-2.57-6.48-2.31-3.67.37-6.69 3.35-7.1 7.02C3.52 15.91 7.27 20 12 20c3.19 0 5.93-1.87 7.21-4.56.32-.67-.16-1.44-.9-1.44-.37 0-.72.2-.88.53-1.13 2.43-3.84 3.97-6.8 3.31-2.22-.49-4.01-2.3-4.48-4.52C5.31 9.44 8.26 6 12 6c1.66 0 3.14.69 4.22 1.78l-1.51 1.51c-.63.63-.19 1.71.7 1.71H19c.55 0 1-.45 1-1V6.41c0-.89-1.08-1.34-1.71-.71l-.64.65z" />
</svg>
);
}
export default ResetMapIcon;

View File

@ -16,35 +16,35 @@ const defaultProps = {
export const blank = {
...defaultProps,
source: blankImage,
id: "Blank Grid 22x22",
id: "__default_blank",
};
export const grass = {
...defaultProps,
source: grassImage,
id: "Grass Grid 22x22",
id: "__default_grass",
};
export const sand = {
...defaultProps,
source: sandImage,
id: "Sand Grid 22x22",
id: "__default_sand",
};
export const stone = {
...defaultProps,
source: stoneImage,
id: "Stone Grid 22x22",
id: "__default_stone",
};
export const water = {
...defaultProps,
source: waterImage,
id: "Water Grid 22x22",
id: "__default_water",
};
export const wood = {
...defaultProps,
source: woodImage,
id: "Wood Grid 22x22",
id: "__default_wood",
};

View File

@ -23,7 +23,7 @@ function SelectMapModal({ isOpen, onRequestClose, onDone }) {
const [currentMap, setCurrentMap] = useState(null);
const [maps, setMaps] = useState(Object.values(defaultMaps));
// Load maps from the database
// Load maps from the database and ensure state is properly setup
useEffect(() => {
async function loadMaps() {
let storedMaps = await db.table("maps").toArray();
@ -35,7 +35,20 @@ function SelectMapModal({ isOpen, onRequestClose, onDone }) {
}
setMaps((prevMaps) => [...storedMaps, ...prevMaps]);
}
async function setupDefaultMapStatesIfNeeded() {
for (let defaultMap of Object.values(defaultMaps)) {
let state = await db.table("states").get(defaultMap.id);
if (!state) {
await db
.table("states")
.add({ ...defaultMapState, mapId: defaultMap.id });
}
}
}
loadMaps();
setupDefaultMapStatesIfNeeded();
}, []);
const [gridX, setGridX] = useState(defaultMapSize);
@ -111,6 +124,10 @@ function SelectMapModal({ isOpen, onRequestClose, onDone }) {
setGridY(map.gridY);
}
async function handleMapReset(id) {
await db.table("states").put({ ...defaultMapState, mapId: id });
}
async function handleSubmit(e) {
e.preventDefault();
if (currentMap) {
@ -199,6 +216,7 @@ function SelectMapModal({ isOpen, onRequestClose, onDone }) {
onMapRemove={handleMapRemove}
selectedMap={currentMap && currentMap.id}
onMapSelect={handleMapSelect}
onMapReset={handleMapReset}
/>
<Flex>
<Box mb={2} mr={1} sx={{ flexGrow: 1 }}>