From 25b215d4e47547d955b34f907a704070ed48d673 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Thu, 23 Apr 2020 17:23:34 +1000 Subject: [PATCH] Added map state to the database --- src/components/map/AddMapButton.js | 6 +- src/components/map/Map.js | 37 +++++----- src/database.js | 2 +- src/helpers/useDebounce.js | 18 +++++ src/modals/AddMapModal.js | 18 ++++- src/routes/Game.js | 104 +++++++++++++++++------------ 6 files changed, 118 insertions(+), 67 deletions(-) create mode 100644 src/helpers/useDebounce.js diff --git a/src/components/map/AddMapButton.js b/src/components/map/AddMapButton.js index ac95b20..75dfcd4 100644 --- a/src/components/map/AddMapButton.js +++ b/src/components/map/AddMapButton.js @@ -13,10 +13,8 @@ function AddMapButton({ onMapChange }) { setIsAddModalOpen(false); } - function handleDone(map) { - if (map) { - onMapChange(map); - } + function handleDone(map, mapState) { + onMapChange(map, mapState); closeModal(); } diff --git a/src/components/map/Map.js b/src/components/map/Map.js index 620ee94..f99d02f 100644 --- a/src/components/map/Map.js +++ b/src/components/map/Map.js @@ -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) => ( - - ))} + {mapState && + Object.values(mapState.tokens).map((token) => ( + + ))} ); @@ -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} diff --git a/src/database.js b/src/database.js index cf9c91f..a88f8f0 100644 --- a/src/database.js +++ b/src/database.js @@ -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; diff --git a/src/helpers/useDebounce.js b/src/helpers/useDebounce.js new file mode 100644 index 0000000..057068c --- /dev/null +++ b/src/helpers/useDebounce.js @@ -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; diff --git a/src/modals/AddMapModal.js b/src/modals/AddMapModal.js index 1e68ecb..53088b0 100644 --- a/src/modals/AddMapModal.js +++ b/src/modals/AddMapModal.js @@ -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) { diff --git a/src/routes/Game.js b/src/routes/Game.js index ef3e955..dca2556 100644 --- a/src/routes/Game.js +++ b/src/routes/Game.js @@ -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() { />