Added token edit modal, refactored map and token data providers
Refactored image drop code into reusable component as well
This commit is contained in:
parent
1774b459dc
commit
7b98370e4c
@ -11,6 +11,8 @@ import ReleaseNotes from "./routes/ReleaseNotes";
|
|||||||
|
|
||||||
import { AuthProvider } from "./contexts/AuthContext";
|
import { AuthProvider } from "./contexts/AuthContext";
|
||||||
import { DatabaseProvider } from "./contexts/DatabaseContext";
|
import { DatabaseProvider } from "./contexts/DatabaseContext";
|
||||||
|
import { MapDataProvider } from "./contexts/MapDataContext";
|
||||||
|
import { TokenDataProvider } from "./contexts/TokenDataContext";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -29,7 +31,11 @@ function App() {
|
|||||||
<FAQ />
|
<FAQ />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/game/:id">
|
<Route path="/game/:id">
|
||||||
|
<MapDataProvider>
|
||||||
|
<TokenDataProvider>
|
||||||
<Game />
|
<Game />
|
||||||
|
</TokenDataProvider>
|
||||||
|
</MapDataProvider>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Home />
|
<Home />
|
||||||
|
61
src/components/ImageDrop.js
Normal file
61
src/components/ImageDrop.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Box, Flex, Text } from "theme-ui";
|
||||||
|
|
||||||
|
function ImageDrop({ onDrop, dropText, children }) {
|
||||||
|
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")) {
|
||||||
|
onDrop(file);
|
||||||
|
}
|
||||||
|
setDragging(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box onDragEnter={handleImageDragEnter}>
|
||||||
|
{children}
|
||||||
|
{dragging && (
|
||||||
|
<Flex
|
||||||
|
bg="overlay"
|
||||||
|
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" }}>
|
||||||
|
{dropText || "Drop image to upload"}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageDrop;
|
@ -13,6 +13,7 @@ import useDataSource from "../../helpers/useDataSource";
|
|||||||
import MapInteraction from "./MapInteraction";
|
import MapInteraction from "./MapInteraction";
|
||||||
|
|
||||||
import AuthContext from "../../contexts/AuthContext";
|
import AuthContext from "../../contexts/AuthContext";
|
||||||
|
import TokenDataContext from "../../contexts/TokenDataContext";
|
||||||
|
|
||||||
import { mapSources as defaultMapSources } from "../../maps";
|
import { mapSources as defaultMapSources } from "../../maps";
|
||||||
|
|
||||||
@ -22,7 +23,6 @@ const mapTokenMenuClassName = "map-token__menu";
|
|||||||
function Map({
|
function Map({
|
||||||
map,
|
map,
|
||||||
mapState,
|
mapState,
|
||||||
tokens,
|
|
||||||
onMapTokenStateChange,
|
onMapTokenStateChange,
|
||||||
onMapTokenStateRemove,
|
onMapTokenStateRemove,
|
||||||
onMapChange,
|
onMapChange,
|
||||||
@ -39,6 +39,8 @@ function Map({
|
|||||||
loading,
|
loading,
|
||||||
}) {
|
}) {
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
|
const { tokens } = useContext(TokenDataContext);
|
||||||
|
|
||||||
const mapSource = useDataSource(map, defaultMapSources);
|
const mapSource = useDataSource(map, defaultMapSources);
|
||||||
|
|
||||||
function handleProxyDragEnd(isOnMap, tokenState) {
|
function handleProxyDragEnd(isOnMap, tokenState) {
|
||||||
|
@ -34,7 +34,7 @@ function MapSettings({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onSettingsChange("gridX", parseInt(e.target.value))
|
onSettingsChange("gridX", parseInt(e.target.value))
|
||||||
}
|
}
|
||||||
disabled={map === null || map.type === "default"}
|
disabled={!map || map.type === "default"}
|
||||||
min={1}
|
min={1}
|
||||||
my={1}
|
my={1}
|
||||||
/>
|
/>
|
||||||
@ -48,7 +48,7 @@ function MapSettings({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onSettingsChange("gridY", parseInt(e.target.value))
|
onSettingsChange("gridY", parseInt(e.target.value))
|
||||||
}
|
}
|
||||||
disabled={map === null || map.type === "default"}
|
disabled={!map || map.type === "default"}
|
||||||
min={1}
|
min={1}
|
||||||
my={1}
|
my={1}
|
||||||
/>
|
/>
|
||||||
@ -61,19 +61,15 @@ function MapSettings({
|
|||||||
<Flex my={1}>
|
<Flex my={1}>
|
||||||
<Label>
|
<Label>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={
|
checked={mapState && mapState.editFlags.includes("fog")}
|
||||||
mapState !== null && mapState.editFlags.includes("fog")
|
disabled={!mapState}
|
||||||
}
|
|
||||||
disabled={mapState === null}
|
|
||||||
onChange={(e) => handleFlagChange(e, "fog")}
|
onChange={(e) => handleFlagChange(e, "fog")}
|
||||||
/>
|
/>
|
||||||
Fog
|
Fog
|
||||||
</Label>
|
</Label>
|
||||||
<Label>
|
<Label>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={
|
checked={mapState && mapState.editFlags.includes("drawing")}
|
||||||
mapState !== null && mapState.editFlags.includes("drawing")
|
|
||||||
}
|
|
||||||
disabled={mapState === null}
|
disabled={mapState === null}
|
||||||
onChange={(e) => handleFlagChange(e, "drawing")}
|
onChange={(e) => handleFlagChange(e, "drawing")}
|
||||||
/>
|
/>
|
||||||
@ -81,10 +77,8 @@ function MapSettings({
|
|||||||
</Label>
|
</Label>
|
||||||
<Label>
|
<Label>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={
|
checked={mapState && mapState.editFlags.includes("tokens")}
|
||||||
mapState !== null && mapState.editFlags.includes("tokens")
|
disabled={!mapState}
|
||||||
}
|
|
||||||
disabled={mapState === null}
|
|
||||||
onChange={(e) => handleFlagChange(e, "tokens")}
|
onChange={(e) => handleFlagChange(e, "tokens")}
|
||||||
/>
|
/>
|
||||||
Tokens
|
Tokens
|
||||||
@ -97,7 +91,7 @@ function MapSettings({
|
|||||||
name="name"
|
name="name"
|
||||||
value={(map && map.name) || ""}
|
value={(map && map.name) || ""}
|
||||||
onChange={(e) => onSettingsChange("name", e.target.value)}
|
onChange={(e) => onSettingsChange("name", e.target.value)}
|
||||||
disabled={map === null || map.type === "default"}
|
disabled={!map || map.type === "default"}
|
||||||
my={1}
|
my={1}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -115,7 +109,7 @@ function MapSettings({
|
|||||||
}}
|
}}
|
||||||
aria-label={showMore ? "Show Less" : "Show More"}
|
aria-label={showMore ? "Show Less" : "Show More"}
|
||||||
title={showMore ? "Show Less" : "Show More"}
|
title={showMore ? "Show Less" : "Show More"}
|
||||||
disabled={map === null}
|
disabled={!map}
|
||||||
>
|
>
|
||||||
<ExpandMoreIcon />
|
<ExpandMoreIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -15,7 +15,7 @@ function MapTile({
|
|||||||
onMapSelect,
|
onMapSelect,
|
||||||
onMapRemove,
|
onMapRemove,
|
||||||
onMapReset,
|
onMapReset,
|
||||||
onSubmit,
|
onDone,
|
||||||
}) {
|
}) {
|
||||||
const mapSource = useDataSource(map, defaultMapSources);
|
const mapSource = useDataSource(map, defaultMapSources);
|
||||||
const [isMapTileMenuOpen, setIsTileMenuOpen] = useState(false);
|
const [isMapTileMenuOpen, setIsTileMenuOpen] = useState(false);
|
||||||
@ -108,7 +108,7 @@ function MapTile({
|
|||||||
}}
|
}}
|
||||||
onDoubleClick={(e) => {
|
onDoubleClick={(e) => {
|
||||||
if (!isMapTileMenuOpen) {
|
if (!isMapTileMenuOpen) {
|
||||||
onSubmit(e);
|
onDone(e);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -17,7 +17,7 @@ function MapTiles({
|
|||||||
onMapAdd,
|
onMapAdd,
|
||||||
onMapRemove,
|
onMapRemove,
|
||||||
onMapReset,
|
onMapReset,
|
||||||
onSubmit,
|
onDone,
|
||||||
}) {
|
}) {
|
||||||
const { databaseStatus } = useContext(DatabaseContext);
|
const { databaseStatus } = useContext(DatabaseContext);
|
||||||
return (
|
return (
|
||||||
@ -69,7 +69,7 @@ function MapTiles({
|
|||||||
onMapSelect={onMapSelect}
|
onMapSelect={onMapSelect}
|
||||||
onMapRemove={onMapRemove}
|
onMapRemove={onMapRemove}
|
||||||
onMapReset={onMapReset}
|
onMapReset={onMapReset}
|
||||||
onSubmit={onSubmit}
|
onDone={onDone}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -5,12 +5,12 @@ import SelectMapModal from "../../modals/SelectMapModal";
|
|||||||
import SelectMapIcon from "../../icons/SelectMapIcon";
|
import SelectMapIcon from "../../icons/SelectMapIcon";
|
||||||
|
|
||||||
function SelectMapButton({ onMapChange, onMapStateChange, currentMap }) {
|
function SelectMapButton({ onMapChange, onMapStateChange, currentMap }) {
|
||||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
function openModal() {
|
function openModal() {
|
||||||
setIsAddModalOpen(true);
|
setIsModalOpen(true);
|
||||||
}
|
}
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
setIsAddModalOpen(false);
|
setIsModalOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDone() {
|
function handleDone() {
|
||||||
@ -27,7 +27,7 @@ function SelectMapButton({ onMapChange, onMapStateChange, currentMap }) {
|
|||||||
<SelectMapIcon />
|
<SelectMapIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<SelectMapModal
|
<SelectMapModal
|
||||||
isOpen={isAddModalOpen}
|
isOpen={isModalOpen}
|
||||||
onRequestClose={closeModal}
|
onRequestClose={closeModal}
|
||||||
onDone={handleDone}
|
onDone={handleDone}
|
||||||
onMapChange={onMapChange}
|
onMapChange={onMapChange}
|
||||||
|
38
src/components/token/SelectTokensButton.js
Normal file
38
src/components/token/SelectTokensButton.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { IconButton } from "theme-ui";
|
||||||
|
|
||||||
|
import SelectTokensIcon from "../../icons/SelectTokensIcon";
|
||||||
|
|
||||||
|
import SelectTokensModal from "../../modals/SelectTokensModal";
|
||||||
|
|
||||||
|
function SelectTokensButton() {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
function openModal() {
|
||||||
|
setIsModalOpen(true);
|
||||||
|
}
|
||||||
|
function closeModal() {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDone() {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Edit Tokens"
|
||||||
|
title="Edit Tokens"
|
||||||
|
onClick={openModal}
|
||||||
|
>
|
||||||
|
<SelectTokensIcon />
|
||||||
|
</IconButton>
|
||||||
|
<SelectTokensModal
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onRequestClose={closeModal}
|
||||||
|
onDone={handleDone}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectTokensButton;
|
59
src/components/token/TokenTile.js
Normal file
59
src/components/token/TokenTile.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Flex, Image, Text } from "theme-ui";
|
||||||
|
|
||||||
|
import useDataSource from "../../helpers/useDataSource";
|
||||||
|
|
||||||
|
import { tokenSources as defaultTokenSources } from "../../tokens";
|
||||||
|
|
||||||
|
function TokenTile({ token, isSelected }) {
|
||||||
|
const tokenSource = useDataSource(token, defaultTokenSources);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
sx={{ width: "100%", height: "100%", objectFit: "contain" }}
|
||||||
|
src={tokenSource}
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background:
|
||||||
|
"linear-gradient(to bottom, rgba(0,0,0,0) 70%, rgba(0,0,0,0.65) 100%);",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
p={2}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
as="p"
|
||||||
|
variant="heading"
|
||||||
|
color="hsl(210, 50%, 96%)"
|
||||||
|
sx={{ textAlign: "center" }}
|
||||||
|
>
|
||||||
|
{token.name}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenTile;
|
55
src/components/token/TokenTiles.js
Normal file
55
src/components/token/TokenTiles.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Flex } from "theme-ui";
|
||||||
|
import SimpleBar from "simplebar-react";
|
||||||
|
|
||||||
|
import AddIcon from "../../icons/AddIcon";
|
||||||
|
|
||||||
|
import TokenTile from "./TokenTile";
|
||||||
|
|
||||||
|
function TokenTiles({ tokens, onTokenAdd }) {
|
||||||
|
return (
|
||||||
|
<SimpleBar style={{ maxHeight: "300px", width: "500px" }}>
|
||||||
|
<Flex
|
||||||
|
py={2}
|
||||||
|
bg="muted"
|
||||||
|
sx={{
|
||||||
|
flexWrap: "wrap",
|
||||||
|
width: "500px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
onClick={onTokenAdd}
|
||||||
|
sx={{
|
||||||
|
":hover": {
|
||||||
|
color: "primary",
|
||||||
|
},
|
||||||
|
":focus": {
|
||||||
|
outline: "none",
|
||||||
|
},
|
||||||
|
":active": {
|
||||||
|
color: "secondary",
|
||||||
|
},
|
||||||
|
width: "150px",
|
||||||
|
height: "150px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
m={2}
|
||||||
|
bg="muted"
|
||||||
|
aria-label="Add Token"
|
||||||
|
title="Add Token"
|
||||||
|
>
|
||||||
|
<AddIcon large />
|
||||||
|
</Flex>
|
||||||
|
{tokens.map((token) => (
|
||||||
|
<TokenTile key={token.id} token={token} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</SimpleBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenTiles;
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useContext } from "react";
|
import React, { useState, useContext } from "react";
|
||||||
import { Box } from "theme-ui";
|
import { Box, Flex } from "theme-ui";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
import SimpleBar from "simplebar-react";
|
import SimpleBar from "simplebar-react";
|
||||||
|
|
||||||
@ -7,23 +7,27 @@ import ListToken from "./ListToken";
|
|||||||
import ProxyToken from "./ProxyToken";
|
import ProxyToken from "./ProxyToken";
|
||||||
import NumberInput from "../NumberInput";
|
import NumberInput from "../NumberInput";
|
||||||
|
|
||||||
|
import SelectTokensButton from "./SelectTokensButton";
|
||||||
|
|
||||||
import { fromEntries } from "../../helpers/shared";
|
import { fromEntries } from "../../helpers/shared";
|
||||||
|
|
||||||
import AuthContext from "../../contexts/AuthContext";
|
import AuthContext from "../../contexts/AuthContext";
|
||||||
|
import TokenDataContext from "../../contexts/TokenDataContext";
|
||||||
|
|
||||||
const listTokenClassName = "list-token";
|
const listTokenClassName = "list-token";
|
||||||
|
|
||||||
function Tokens({ onCreateMapTokenState, tokens }) {
|
function Tokens({ onMapTokenStateCreate }) {
|
||||||
const [tokenSize, setTokenSize] = useState(1);
|
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
|
const { tokens } = useContext(TokenDataContext);
|
||||||
|
|
||||||
|
const [tokenSize, setTokenSize] = useState(1);
|
||||||
|
|
||||||
function handleProxyDragEnd(isOnMap, token) {
|
function handleProxyDragEnd(isOnMap, token) {
|
||||||
if (isOnMap && onCreateMapTokenState) {
|
if (isOnMap && onMapTokenStateCreate) {
|
||||||
// Create a token state from the dragged token
|
// Create a token state from the dragged token
|
||||||
onCreateMapTokenState({
|
onMapTokenStateCreate({
|
||||||
id: shortid.generate(),
|
id: shortid.generate(),
|
||||||
tokenId: token.id,
|
tokenId: token.id,
|
||||||
type: token.type,
|
|
||||||
owner: userId,
|
owner: userId,
|
||||||
size: tokenSize,
|
size: tokenSize,
|
||||||
label: "",
|
label: "",
|
||||||
@ -44,7 +48,7 @@ function Tokens({ onCreateMapTokenState, tokens }) {
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SimpleBar style={{ height: "calc(100% - 58px)", overflowX: "hidden" }}>
|
<SimpleBar style={{ height: "calc(100% - 48px)", overflowX: "hidden" }}>
|
||||||
{tokens.map((token) => (
|
{tokens.map((token) => (
|
||||||
<ListToken
|
<ListToken
|
||||||
key={token.id}
|
key={token.id}
|
||||||
@ -53,15 +57,23 @@ function Tokens({ onCreateMapTokenState, tokens }) {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</SimpleBar>
|
</SimpleBar>
|
||||||
<Box pt={1} bg="muted" sx={{ height: "58px" }}>
|
<Flex
|
||||||
<NumberInput
|
bg="muted"
|
||||||
|
sx={{
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "48px",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTokensButton />
|
||||||
|
{/* <NumberInput
|
||||||
value={tokenSize}
|
value={tokenSize}
|
||||||
onChange={setTokenSize}
|
onChange={setTokenSize}
|
||||||
title="Size"
|
title="Size"
|
||||||
min={1}
|
min={1}
|
||||||
max={9}
|
max={9}
|
||||||
/>
|
/> */}
|
||||||
</Box>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<ProxyToken
|
<ProxyToken
|
||||||
tokenClassName={listTokenClassName}
|
tokenClassName={listTokenClassName}
|
||||||
|
146
src/contexts/MapDataContext.js
Normal file
146
src/contexts/MapDataContext.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import React, { useEffect, useState, useContext } from "react";
|
||||||
|
|
||||||
|
import AuthContext from "./AuthContext";
|
||||||
|
import DatabaseContext from "./DatabaseContext";
|
||||||
|
|
||||||
|
import { maps as defaultMaps } from "../maps";
|
||||||
|
|
||||||
|
const MapDataContext = React.createContext();
|
||||||
|
|
||||||
|
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"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MapDataProvider({ children }) {
|
||||||
|
const { database } = useContext(DatabaseContext);
|
||||||
|
const { userId } = useContext(AuthContext);
|
||||||
|
|
||||||
|
const [maps, setMaps] = useState([]);
|
||||||
|
const [mapStates, setMapStates] = 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,
|
||||||
|
gridType: "grid",
|
||||||
|
});
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
const storedStates = await database.table("states").toArray();
|
||||||
|
setMapStates(storedStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMaps();
|
||||||
|
}, [userId, database]);
|
||||||
|
|
||||||
|
async function addMap(map) {
|
||||||
|
await database.table("maps").add(map);
|
||||||
|
const state = { ...defaultMapState, mapId: map.id };
|
||||||
|
await database.table("states").add(state);
|
||||||
|
setMaps((prevMaps) => [map, ...prevMaps]);
|
||||||
|
setMapStates((prevStates) => [state, ...prevStates]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeMap(id) {
|
||||||
|
await database.table("maps").delete(id);
|
||||||
|
await database.table("states").delete(id);
|
||||||
|
setMaps((prevMaps) => {
|
||||||
|
const filtered = prevMaps.filter((map) => map.id !== id);
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
setMapStates((prevMapsStates) => {
|
||||||
|
const filtered = prevMapsStates.filter((state) => state.mapId !== id);
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetMap(id) {
|
||||||
|
const state = { ...defaultMapState, mapId: id };
|
||||||
|
await database.table("states").put(state);
|
||||||
|
setMapStates((prevMapStates) => {
|
||||||
|
const newStates = [...prevMapStates];
|
||||||
|
const i = newStates.findIndex((state) => state.mapId === id);
|
||||||
|
if (i > -1) {
|
||||||
|
newStates[i] = state;
|
||||||
|
}
|
||||||
|
return newStates;
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateMap(id, update) {
|
||||||
|
const change = { ...update, lastModified: Date.now() };
|
||||||
|
await database.table("maps").update(id, change);
|
||||||
|
setMaps((prevMaps) => {
|
||||||
|
const newMaps = [...prevMaps];
|
||||||
|
const i = newMaps.findIndex((map) => map.id === id);
|
||||||
|
if (i > -1) {
|
||||||
|
newMaps[i] = { ...newMaps[i], ...change };
|
||||||
|
}
|
||||||
|
return newMaps;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateMapState(id, update) {
|
||||||
|
await database.table("states").update(id, update);
|
||||||
|
setMapStates((prevMapStates) => {
|
||||||
|
const newStates = [...prevMapStates];
|
||||||
|
const i = newStates.findIndex((state) => state.mapId === id);
|
||||||
|
if (i > -1) {
|
||||||
|
newStates[i] = { ...newStates[i], ...update };
|
||||||
|
}
|
||||||
|
return newStates;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
maps,
|
||||||
|
mapStates,
|
||||||
|
addMap,
|
||||||
|
removeMap,
|
||||||
|
resetMap,
|
||||||
|
updateMap,
|
||||||
|
updateMapState,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<MapDataContext.Provider value={value}>{children}</MapDataContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MapDataContext;
|
40
src/contexts/TokenDataContext.js
Normal file
40
src/contexts/TokenDataContext.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React, { useEffect, useState, useContext } from "react";
|
||||||
|
|
||||||
|
import AuthContext from "./AuthContext";
|
||||||
|
import DatabaseContext from "./DatabaseContext";
|
||||||
|
|
||||||
|
import { tokens as defaultTokens } from "../tokens";
|
||||||
|
|
||||||
|
const TokenDataContext = React.createContext();
|
||||||
|
|
||||||
|
export function TokenDataProvider({ children }) {
|
||||||
|
const { database } = useContext(DatabaseContext);
|
||||||
|
const { userId } = useContext(AuthContext);
|
||||||
|
|
||||||
|
const [tokens, setTokens] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const defaultTokensWithIds = [];
|
||||||
|
for (let defaultToken of defaultTokens) {
|
||||||
|
defaultTokensWithIds.push({
|
||||||
|
...defaultToken,
|
||||||
|
id: `__default-${defaultToken.key}`,
|
||||||
|
owner: userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTokens(defaultTokensWithIds);
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
const value = { tokens };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TokenDataContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</TokenDataContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TokenDataContext;
|
18
src/icons/SelectTokensIcon.js
Normal file
18
src/icons/SelectTokensIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function SelectMapIcon() {
|
||||||
|
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 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4 11h-3v3c0 .55-.45 1-1 1s-1-.45-1-1v-3H8c-.55 0-1-.45-1-1s.45-1 1-1h3V8c0-.55.45-1 1-1s1 .45 1 1v3h3c.55 0 1 .45 1 1s-.45 1-1 1z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectMapIcon;
|
@ -1,32 +1,18 @@
|
|||||||
import React, { useRef, useState, useEffect, useContext } from "react";
|
import React, { useRef, useState, useContext } from "react";
|
||||||
import { Box, Button, Flex, Label, Text } from "theme-ui";
|
import { Button, Flex, Label } from "theme-ui";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
|
||||||
import Modal from "../components/Modal";
|
import Modal from "../components/Modal";
|
||||||
import MapTiles from "../components/map/MapTiles";
|
import MapTiles from "../components/map/MapTiles";
|
||||||
import MapSettings from "../components/map/MapSettings";
|
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 blobToBuffer from "../helpers/blobToBuffer";
|
||||||
|
|
||||||
import { maps as defaultMaps } from "../maps";
|
import MapDataContext from "../contexts/MapDataContext";
|
||||||
|
import AuthContext from "../contexts/AuthContext";
|
||||||
|
|
||||||
const defaultMapSize = 22;
|
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 = {
|
const defaultMapProps = {
|
||||||
// Grid type
|
// Grid type
|
||||||
// TODO: add support for hex horizontal and hex vertical
|
// TODO: add support for hex horizontal and hex vertical
|
||||||
@ -42,68 +28,26 @@ function SelectMapModal({
|
|||||||
// The map currently being view in the map screen
|
// The map currently being view in the map screen
|
||||||
currentMap,
|
currentMap,
|
||||||
}) {
|
}) {
|
||||||
const { database } = useContext(DatabaseContext);
|
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
|
const {
|
||||||
const wasOpen = usePrevious(isOpen);
|
maps,
|
||||||
|
mapStates,
|
||||||
|
addMap,
|
||||||
|
removeMap,
|
||||||
|
resetMap,
|
||||||
|
updateMap,
|
||||||
|
updateMapState,
|
||||||
|
} = useContext(MapDataContext);
|
||||||
|
|
||||||
const [imageLoading, setImageLoading] = useState(false);
|
const [imageLoading, setImageLoading] = useState(false);
|
||||||
|
|
||||||
// The map selected in the modal
|
// The map selected in the modal
|
||||||
const [selectedMap, setSelectedMap] = useState(null);
|
const [selectedMapId, setSelectedMapId] = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadMaps() {
|
const selectedMap = maps.find((map) => map.id === selectedMapId);
|
||||||
let storedMaps = await database
|
const selectedMapState = mapStates.find(
|
||||||
.table("maps")
|
(state) => state.mapId === selectedMapId
|
||||||
.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 fileInputRef = useRef();
|
const fileInputRef = useRef();
|
||||||
|
|
||||||
@ -180,108 +124,55 @@ function SelectMapModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapAdd(map) {
|
async function handleMapAdd(map) {
|
||||||
await database.table("maps").add(map);
|
await addMap(map);
|
||||||
const state = { ...defaultMapState, mapId: map.id };
|
setSelectedMapId(map.id);
|
||||||
await database.table("states").add(state);
|
|
||||||
setMaps((prevMaps) => [map, ...prevMaps]);
|
|
||||||
setSelectedMap(map);
|
|
||||||
setSelectedMapState(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapRemove(id) {
|
async function handleMapRemove(id) {
|
||||||
await database.table("maps").delete(id);
|
await removeMap(id);
|
||||||
await database.table("states").delete(id);
|
setSelectedMapId(null);
|
||||||
setMaps((prevMaps) => {
|
|
||||||
const filtered = prevMaps.filter((map) => map.id !== id);
|
|
||||||
setSelectedMap(filtered[0]);
|
|
||||||
database.table("states").get(filtered[0].id).then(setSelectedMapState);
|
|
||||||
return filtered;
|
|
||||||
});
|
|
||||||
// Removed the map from the map screen if needed
|
// Removed the map from the map screen if needed
|
||||||
if (currentMap && currentMap.id === selectedMap.id) {
|
if (currentMap && currentMap.id === selectedMapId) {
|
||||||
onMapChange(null, null);
|
onMapChange(null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapSelect(map) {
|
function handleMapSelect(map) {
|
||||||
const state = await database.table("states").get(map.id);
|
setSelectedMapId(map.id);
|
||||||
setSelectedMapState(state);
|
|
||||||
setSelectedMap(map);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapReset(id) {
|
async function handleMapReset(id) {
|
||||||
const state = { ...defaultMapState, mapId: id };
|
const newState = await resetMap(id);
|
||||||
await database.table("states").put(state);
|
|
||||||
setSelectedMapState(state);
|
|
||||||
// Reset the state of the current map if needed
|
// Reset the state of the current map if needed
|
||||||
if (currentMap && currentMap.id === selectedMap.id) {
|
if (currentMap && currentMap.id === selectedMapId) {
|
||||||
onMapStateChange(state);
|
onMapStateChange(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit(e) {
|
async function handleDone() {
|
||||||
e.preventDefault();
|
if (selectedMapId) {
|
||||||
if (selectedMap) {
|
|
||||||
onMapChange(selectedMap, selectedMapState);
|
onMapChange(selectedMap, selectedMapState);
|
||||||
onDone();
|
onDone();
|
||||||
}
|
}
|
||||||
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
|
* Map settings
|
||||||
*/
|
*/
|
||||||
const [showMoreSettings, setShowMoreSettings] = useState(false);
|
const [showMoreSettings, setShowMoreSettings] = useState(false);
|
||||||
|
|
||||||
async function handleMapSettingsChange(key, value) {
|
async function handleMapSettingsChange(key, value) {
|
||||||
const change = { [key]: value, lastModified: Date.now() };
|
await updateMap(selectedMapId, { [key]: value });
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapStateSettingsChange(key, value) {
|
async function handleMapStateSettingsChange(key, value) {
|
||||||
database.table("states").update(selectedMap.id, { [key]: value });
|
await updateMapState(selectedMapId, { [key]: value });
|
||||||
setSelectedMapState((prevState) => ({ ...prevState, [key]: value }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onRequestClose={onRequestClose}>
|
<Modal isOpen={isOpen} onRequestClose={onRequestClose}>
|
||||||
<Box as="form" onSubmit={handleSubmit} onDragEnter={handleImageDragEnter}>
|
<ImageDrop onDrop={handleImageUpload} dropText="Drop map to upload">
|
||||||
<input
|
<input
|
||||||
onChange={(event) => handleImageUpload(event.target.files[0])}
|
onChange={(event) => handleImageUpload(event.target.files[0])}
|
||||||
type="file"
|
type="file"
|
||||||
@ -305,7 +196,7 @@ function SelectMapModal({
|
|||||||
selectedMapState={selectedMapState}
|
selectedMapState={selectedMapState}
|
||||||
onMapSelect={handleMapSelect}
|
onMapSelect={handleMapSelect}
|
||||||
onMapReset={handleMapReset}
|
onMapReset={handleMapReset}
|
||||||
onSubmit={handleSubmit}
|
onDone={handleDone}
|
||||||
/>
|
/>
|
||||||
<MapSettings
|
<MapSettings
|
||||||
map={selectedMap}
|
map={selectedMap}
|
||||||
@ -315,35 +206,15 @@ function SelectMapModal({
|
|||||||
showMore={showMoreSettings}
|
showMore={showMoreSettings}
|
||||||
onShowMoreChange={setShowMoreSettings}
|
onShowMoreChange={setShowMoreSettings}
|
||||||
/>
|
/>
|
||||||
<Button variant="primary" disabled={imageLoading}>
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
disabled={imageLoading}
|
||||||
|
onClick={handleDone}
|
||||||
|
>
|
||||||
Done
|
Done
|
||||||
</Button>
|
</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>
|
||||||
)}
|
</ImageDrop>
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
48
src/modals/SelectTokensModal.js
Normal file
48
src/modals/SelectTokensModal.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React, { useRef, useContext } from "react";
|
||||||
|
import { Flex, Label } from "theme-ui";
|
||||||
|
|
||||||
|
import Modal from "../components/Modal";
|
||||||
|
import ImageDrop from "../components/ImageDrop";
|
||||||
|
|
||||||
|
import TokenTiles from "../components/token/TokenTiles";
|
||||||
|
|
||||||
|
import TokenDataContext from "../contexts/TokenDataContext";
|
||||||
|
|
||||||
|
function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||||
|
const { tokens } = useContext(TokenDataContext);
|
||||||
|
const fileInputRef = useRef();
|
||||||
|
|
||||||
|
function openImageDialog() {
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImageUpload(image) {}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onRequestClose={onRequestClose}>
|
||||||
|
<ImageDrop onDrop={handleImageUpload} dropText="Drop token to upload">
|
||||||
|
<input
|
||||||
|
onChange={(event) => handleImageUpload(event.target.files[0])}
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
style={{ display: "none" }}
|
||||||
|
ref={fileInputRef}
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label pt={2} pb={1}>
|
||||||
|
Edit or import a token
|
||||||
|
</Label>
|
||||||
|
<TokenTiles tokens={tokens} onTokenAdd={openImageDialog} />
|
||||||
|
</Flex>
|
||||||
|
</ImageDrop>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectTokensModal;
|
@ -17,8 +17,7 @@ import AuthModal from "../modals/AuthModal";
|
|||||||
|
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
import DatabaseContext from "../contexts/DatabaseContext";
|
import DatabaseContext from "../contexts/DatabaseContext";
|
||||||
|
import MapDataContext from "../contexts/MapDataContext";
|
||||||
import { tokens as defaultTokens } from "../tokens";
|
|
||||||
|
|
||||||
function Game() {
|
function Game() {
|
||||||
const { database } = useContext(DatabaseContext);
|
const { database } = useContext(DatabaseContext);
|
||||||
@ -71,6 +70,7 @@ function Game() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { updateMapState } = useContext(MapDataContext);
|
||||||
// Sync the map state to the database after 500ms of inactivity
|
// Sync the map state to the database after 500ms of inactivity
|
||||||
const debouncedMapState = useDebounce(mapState, 500);
|
const debouncedMapState = useDebounce(mapState, 500);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -81,11 +81,9 @@ function Game() {
|
|||||||
map.owner === userId &&
|
map.owner === userId &&
|
||||||
database
|
database
|
||||||
) {
|
) {
|
||||||
database
|
updateMapState(debouncedMapState.mapId, debouncedMapState);
|
||||||
.table("states")
|
|
||||||
.update(debouncedMapState.mapId, debouncedMapState);
|
|
||||||
}
|
}
|
||||||
}, [map, debouncedMapState, userId, database]);
|
}, [map, debouncedMapState, userId, database, updateMapState]);
|
||||||
|
|
||||||
function handleMapChange(newMap, newMapState) {
|
function handleMapChange(newMap, newMapState) {
|
||||||
setMapState(newMapState);
|
setMapState(newMapState);
|
||||||
@ -116,7 +114,7 @@ function Game() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapTokenStateChange(token) {
|
function handleMapTokenStateChange(token) {
|
||||||
if (mapState === null) {
|
if (mapState === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -438,30 +436,15 @@ function Game() {
|
|||||||
}
|
}
|
||||||
}, [stream, peers, handleStreamEnd]);
|
}, [stream, peers, handleStreamEnd]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Token data
|
|
||||||
*/
|
|
||||||
const [tokens, setTokens] = useState([]);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!userId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const defaultTokensWithIds = [];
|
|
||||||
for (let defaultToken of defaultTokens) {
|
|
||||||
defaultTokensWithIds.push({
|
|
||||||
...defaultToken,
|
|
||||||
id: `__default-${defaultToken.key}`,
|
|
||||||
owner: userId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setTokens(defaultTokensWithIds);
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex sx={{ flexDirection: "column", height: "100%" }}>
|
<Flex sx={{ flexDirection: "column", height: "100%" }}>
|
||||||
<Flex
|
<Flex
|
||||||
sx={{ justifyContent: "space-between", flexGrow: 1, height: "100%" }}
|
sx={{
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexGrow: 1,
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Party
|
<Party
|
||||||
nickname={nickname}
|
nickname={nickname}
|
||||||
@ -476,7 +459,6 @@ function Game() {
|
|||||||
<Map
|
<Map
|
||||||
map={map}
|
map={map}
|
||||||
mapState={mapState}
|
mapState={mapState}
|
||||||
tokens={tokens}
|
|
||||||
loading={mapLoading}
|
loading={mapLoading}
|
||||||
onMapTokenStateChange={handleMapTokenStateChange}
|
onMapTokenStateChange={handleMapTokenStateChange}
|
||||||
onMapTokenStateRemove={handleMapTokenStateRemove}
|
onMapTokenStateRemove={handleMapTokenStateRemove}
|
||||||
@ -492,10 +474,7 @@ function Game() {
|
|||||||
allowFogDrawing={canEditFogDrawing}
|
allowFogDrawing={canEditFogDrawing}
|
||||||
disabledTokens={disabledMapTokens}
|
disabledTokens={disabledMapTokens}
|
||||||
/>
|
/>
|
||||||
<Tokens
|
<Tokens onMapTokenStateCreate={handleMapTokenStateChange} />
|
||||||
tokens={tokens}
|
|
||||||
onCreateMapTokenState={handleMapTokenStateChange}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Banner isOpen={!!peerError} onRequestClose={() => setPeerError(null)}>
|
<Banner isOpen={!!peerError} onRequestClose={() => setPeerError(null)}>
|
||||||
|
Loading…
Reference in New Issue
Block a user