Added map state to the database

This commit is contained in:
Mitchell McCaffrey 2020-04-23 17:23:34 +10:00
parent 071cd3ea7f
commit 25b215d4e4
6 changed files with 118 additions and 67 deletions

View File

@ -13,10 +13,8 @@ function AddMapButton({ onMapChange }) {
setIsAddModalOpen(false);
}
function handleDone(map) {
if (map) {
onMapChange(map);
}
function handleDone(map, mapState) {
onMapChange(map, mapState);
closeModal();
}

View File

@ -18,15 +18,13 @@ const maxZoom = 5;
function Map({
map,
tokens,
mapState,
onMapTokenChange,
onMapTokenRemove,
onMapChange,
onMapDraw,
onMapDrawUndo,
onMapDrawRedo,
drawActions,
drawActionIndex,
}) {
function handleProxyDragEnd(isOnMap, token) {
if (isOnMap && onMapTokenChange) {
@ -63,9 +61,12 @@ function Map({
// Replay the draw actions and convert them to shapes for the map drawing
useEffect(() => {
if (!mapState) {
return;
}
let shapesById = {};
for (let i = 0; i <= drawActionIndex; i++) {
const action = drawActions[i];
for (let i = 0; i <= mapState.drawActionIndex; i++) {
const action = mapState.drawActions[i];
if (action.type === "add") {
for (let shape of action.shapes) {
shapesById[shape.id] = shape;
@ -76,7 +77,7 @@ function Map({
}
}
setDrawnShapes(Object.values(shapesById));
}, [drawActions, drawActionIndex]);
}, [mapState]);
const disabledTools = [];
if (!map) {
@ -233,14 +234,15 @@ function Map({
pointerEvents: "none",
}}
>
{Object.values(tokens).map((token) => (
<MapToken
key={token.id}
token={token}
tokenSizePercent={tokenSizePercent}
className={`${mapTokenProxyClassName} ${mapTokenMenuClassName}`}
/>
))}
{mapState &&
Object.values(mapState.tokens).map((token) => (
<MapToken
key={token.id}
token={token}
tokenSizePercent={tokenSizePercent}
className={`${mapTokenProxyClassName} ${mapTokenMenuClassName}`}
/>
))}
</Box>
);
@ -299,8 +301,11 @@ function Map({
disabledTools={disabledTools}
onUndo={onMapDrawUndo}
onRedo={onMapDrawRedo}
undoDisabled={drawActionIndex < 0}
redoDisabled={drawActionIndex === drawActions.length - 1}
undoDisabled={!mapState || mapState.drawActionIndex < 0}
redoDisabled={
!mapState ||
mapState.drawActionIndex === mapState.drawActions.length - 1
}
brushColor={brushColor}
onBrushColorChange={setBrushColor}
onEraseAll={handleShapeRemoveAll}

View File

@ -1,6 +1,6 @@
import Dexie from "dexie";
const db = new Dexie("OwlbearRodeoDB");
db.version(1).stores({ maps: "id" });
db.version(1).stores({ maps: "id", states: "mapId" });
export default db;

View File

@ -0,0 +1,18 @@
import { useEffect, useState } from "react";
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timeout);
};
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;

View File

@ -10,6 +10,13 @@ import MapSelect from "../components/map/MapSelect";
import * as defaultMaps from "../maps";
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)
drawActionIndex: -1,
drawActions: [],
};
function AddMapModal({ isOpen, onRequestClose, onDone }) {
const [imageLoading, setImageLoading] = useState(false);
@ -81,6 +88,7 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
async function handleMapAdd(map) {
await db.table("maps").add(map);
await db.table("states").add({ ...defaultMapState, mapId: map.id });
setMaps((prevMaps) => [map, ...prevMaps]);
setCurrentMapId(map.id);
setGridX(map.gridX);
@ -89,6 +97,7 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
async function handleMapRemove(id) {
await db.table("maps").delete(id);
await db.table("states").delete(id);
setMaps((prevMaps) => {
const filtered = prevMaps.filter((map) => map.id !== id);
setCurrentMapId(filtered[0].id);
@ -102,9 +111,14 @@ function AddMapModal({ isOpen, onRequestClose, onDone }) {
setGridY(map.gridY);
}
function handleSubmit(e) {
async function handleSubmit(e) {
e.preventDefault();
onDone(maps.find((map) => map.id === currentMapId));
const currentMap = maps.find((map) => map.id === currentMapId);
if (currentMap) {
let currentMapState =
(await db.table("states").get(currentMap.id)) || defaultMapState;
onDone(currentMap, currentMapState);
}
}
async function handleGridXChange(e) {

View File

@ -2,9 +2,12 @@ import React, { useState, useEffect, useCallback, useContext } from "react";
import { Flex, Box, Text, Link } from "theme-ui";
import { useParams } from "react-router-dom";
import db from "../database";
import { omit, isStreamStopped } from "../helpers/shared";
import useSession from "../helpers/useSession";
import useNickname from "../helpers/useNickname";
import useDebounce from "../helpers/useDebounce";
import Party from "../components/party/Party";
import Tokens from "../components/token/Tokens";
@ -35,23 +38,32 @@ function Game() {
*/
const [map, setMap] = useState(null);
const [mapState, setMapState] = useState(null);
function handleMapChange(newMap) {
// Sync the map state to the database after 500ms of inactivity
const debouncedMapState = useDebounce(mapState, 500);
useEffect(() => {
if (debouncedMapState && debouncedMapState.mapId) {
db.table("states").update(debouncedMapState.mapId, debouncedMapState);
}
}, [debouncedMapState]);
function handleMapChange(newMap, newMapState) {
setMap(newMap);
setMapState(newMapState);
for (let peer of Object.values(peers)) {
peer.connection.send({ id: "mapState", data: newMapState });
peer.connection.send({ id: "map", data: newMap });
}
}
const [mapTokens, setMapTokens] = useState({});
function handleMapTokenChange(token) {
if (!map.source) {
return;
}
setMapTokens((prevMapTokens) => ({
...prevMapTokens,
[token.id]: token,
async function handleMapTokenChange(token) {
setMapState((prevMapState) => ({
...prevMapState,
tokens: {
...prevMapState.tokens,
[token.id]: token,
},
}));
for (let peer of Object.values(peers)) {
const data = { [token.id]: token };
@ -60,9 +72,9 @@ function Game() {
}
function handleMapTokenRemove(token) {
setMapTokens((prevMapTokens) => {
const { [token.id]: old, ...rest } = prevMapTokens;
return rest;
setMapState((prevMapState) => {
const { [token.id]: old, ...rest } = prevMapState.tokens;
return { ...prevMapState, tokens: rest };
});
for (let peer of Object.values(peers)) {
const data = { [token.id]: token };
@ -70,19 +82,18 @@ function Game() {
}
}
const [mapDrawActions, setMapDrawActions] = useState([]);
// An index into the draw actions array to which only actions before the
// index will be performed (used in undo and redo)
const [mapDrawActionIndex, setMapDrawActionIndex] = useState(-1);
function addNewMapDrawActions(actions) {
setMapDrawActions((prevActions) => {
setMapState((prevMapState) => {
const newActions = [
...prevActions.slice(0, mapDrawActionIndex + 1),
...prevMapState.drawActions.slice(0, prevMapState.drawActionIndex + 1),
...actions,
];
const newIndex = newActions.length - 1;
setMapDrawActionIndex(newIndex);
return newActions;
return {
...prevMapState,
drawActions: newActions,
drawActionIndex: newIndex,
};
});
}
@ -94,8 +105,11 @@ function Game() {
}
function handleMapDrawUndo() {
const newIndex = Math.max(mapDrawActionIndex - 1, -1);
setMapDrawActionIndex(newIndex);
const newIndex = Math.max(mapState.drawActionIndex - 1, -1);
setMapState((prevMapState) => ({
...prevMapState,
drawActionIndex: newIndex,
}));
for (let peer of Object.values(peers)) {
peer.connection.send({ id: "mapDrawIndex", data: newIndex });
}
@ -103,10 +117,13 @@ function Game() {
function handleMapDrawRedo() {
const newIndex = Math.min(
mapDrawActionIndex + 1,
mapDrawActions.length - 1
mapState.drawActionIndex + 1,
mapState.drawActions.length - 1
);
setMapDrawActionIndex(newIndex);
setMapState((prevMapState) => ({
...prevMapState,
drawActionIndex: newIndex,
}));
for (let peer of Object.values(peers)) {
peer.connection.send({ id: "mapDrawIndex", data: newIndex });
}
@ -145,14 +162,8 @@ function Game() {
if (map) {
peer.connection.send({ id: "map", data: map });
}
if (mapTokens) {
peer.connection.send({ id: "tokenEdit", data: mapTokens });
}
if (mapDrawActions) {
peer.connection.send({ id: "mapDraw", data: mapDrawActions });
}
if (mapDrawActionIndex !== mapDrawActions.length - 1) {
peer.connection.send({ id: "mapDrawIndex", data: mapDrawActionIndex });
if (mapState) {
peer.connection.send({ id: "mapState", data: mapState });
}
}
if (data.id === "map") {
@ -166,16 +177,20 @@ function Game() {
setMap(data.data);
}
}
if (data.id === "mapState") {
setMapState(data.data);
}
if (data.id === "tokenEdit") {
setMapTokens((prevMapTokens) => ({
...prevMapTokens,
...data.data,
setMapState((prevMapState) => ({
...prevMapState,
tokens: { ...prevMapState.tokens, ...data.data },
}));
}
if (data.id === "tokenRemove") {
setMapTokens((prevMapTokens) =>
omit(prevMapTokens, Object.keys(data.data))
);
setMapState((prevMapState) => ({
...prevMapState,
tokens: omit(prevMapState.tokens, Object.keys(data.data)),
}));
}
if (data.id === "nickname") {
setPartyNicknames((prevNicknames) => ({
@ -187,7 +202,10 @@ function Game() {
addNewMapDrawActions(data.data);
}
if (data.id === "mapDrawIndex") {
setMapDrawActionIndex(data.data);
setMapState((prevMapState) => ({
...prevMapState,
drawActionIndex: data.data,
}));
}
}
@ -301,15 +319,13 @@ function Game() {
/>
<Map
map={map}
tokens={mapTokens}
mapState={mapState}
onMapTokenChange={handleMapTokenChange}
onMapTokenRemove={handleMapTokenRemove}
onMapChange={handleMapChange}
onMapDraw={handleMapDraw}
onMapDrawUndo={handleMapDrawUndo}
onMapDrawRedo={handleMapDrawRedo}
drawActions={mapDrawActions}
drawActionIndex={mapDrawActionIndex}
/>
<Tokens onCreateMapToken={handleMapTokenChange} />
</Flex>