From cf9e4284471f3a5cc99ebb8decb7e6de8fd9e08f Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Sun, 14 Feb 2021 18:35:42 +1100 Subject: [PATCH] Moved add/remove maps/tokens to Observables to allow for cross session syncing --- package.json | 1 + src/contexts/MapDataContext.js | 72 ++++++++++++++++++++++---------- src/contexts/TokenDataContext.js | 41 ++++++++++++++---- src/database.js | 7 +++- src/modals/EditMapModal.js | 2 +- src/modals/EditTokenModal.js | 2 +- src/modals/ImportExportModal.js | 1 - yarn.lock | 5 +++ 8 files changed, 97 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index e700bdd..927f659 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "comlink": "^4.3.0", "deep-diff": "^1.0.2", "dexie": "^3.0.3", + "dexie-observable": "^3.0.0-beta.10", "err-code": "^2.0.3", "fake-indexeddb": "^3.1.2", "file-saver": "^2.0.5", diff --git a/src/contexts/MapDataContext.js b/src/contexts/MapDataContext.js index d804568..8c45a2f 100644 --- a/src/contexts/MapDataContext.js +++ b/src/contexts/MapDataContext.js @@ -112,6 +112,14 @@ export function MapDataProvider({ children }) { [database] ); + const getMapStateFromDB = useCallback( + async (mapId) => { + let mapState = await database.table("states").get(mapId); + return mapState; + }, + [database] + ); + /** * Keep up to cachedMapMax amount of maps that you don't own * Sorted by when they we're last used @@ -140,11 +148,10 @@ export function MapDataProvider({ children }) { */ const addMap = useCallback( async (map) => { - await database.table("maps").add(map); + // Just update map database as react state will be updated with an Observable const state = { ...defaultMapState, mapId: map.id }; + await database.table("maps").add(map); await database.table("states").add(state); - setMaps((prevMaps) => [map, ...prevMaps]); - setMapStates((prevStates) => [state, ...prevStates]); if (map.owner !== userId) { await updateCache(); } @@ -156,14 +163,6 @@ export function MapDataProvider({ children }) { async (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; - }); }, [database] ); @@ -172,16 +171,6 @@ export function MapDataProvider({ children }) { async (ids) => { await database.table("maps").bulkDelete(ids); await database.table("states").bulkDelete(ids); - setMaps((prevMaps) => { - const filtered = prevMaps.filter((map) => !ids.includes(map.id)); - return filtered; - }); - setMapStates((prevMapsStates) => { - const filtered = prevMapsStates.filter( - (state) => !ids.includes(state.mapId) - ); - return filtered; - }); }, [database] ); @@ -284,6 +273,46 @@ export function MapDataProvider({ children }) { [database, updateCache, userId] ); + // Create DB observable to sync creating and deleting + useEffect(() => { + if (!database || databaseStatus === "loading") { + return; + } + + function handleMapChanges(changes) { + for (let change of changes) { + if (change.table === "maps") { + if (change.type === 1) { + // Created + const map = change.obj; + const state = { ...defaultMapState, mapId: map.id }; + setMaps((prevMaps) => [map, ...prevMaps]); + setMapStates((prevStates) => [state, ...prevStates]); + } else if (change.type === 3) { + // Deleted + const id = change.key; + setMaps((prevMaps) => { + const filtered = prevMaps.filter((map) => map.id !== id); + return filtered; + }); + setMapStates((prevMapsStates) => { + const filtered = prevMapsStates.filter( + (state) => state.mapId !== id + ); + return filtered; + }); + } + } + } + } + + database.on("changes", handleMapChanges); + + return () => { + database.on("changes").unsubscribe(handleMapChanges); + }; + }, [database, databaseStatus]); + const ownedMaps = maps.filter((map) => map.owner === userId); const value = { @@ -301,6 +330,7 @@ export function MapDataProvider({ children }) { getMap, getMapFromDB, mapsLoading, + getMapStateFromDB, }; return ( {children} diff --git a/src/contexts/TokenDataContext.js b/src/contexts/TokenDataContext.js index 03c52fc..b87166c 100644 --- a/src/contexts/TokenDataContext.js +++ b/src/contexts/TokenDataContext.js @@ -115,7 +115,6 @@ export function TokenDataProvider({ children }) { const addToken = useCallback( async (token) => { await database.table("tokens").add(token); - setTokens((prevTokens) => [token, ...prevTokens]); if (token.owner !== userId) { await updateCache(); } @@ -126,10 +125,6 @@ export function TokenDataProvider({ children }) { const removeToken = useCallback( async (id) => { await database.table("tokens").delete(id); - setTokens((prevTokens) => { - const filtered = prevTokens.filter((token) => token.id !== id); - return filtered; - }); }, [database] ); @@ -137,10 +132,6 @@ export function TokenDataProvider({ children }) { const removeTokens = useCallback( async (ids) => { await database.table("tokens").bulkDelete(ids); - setTokens((prevTokens) => { - const filtered = prevTokens.filter((token) => !ids.includes(token.id)); - return filtered; - }); }, [database] ); @@ -230,6 +221,38 @@ export function TokenDataProvider({ children }) { }); }, []); + // Create DB observable to sync creating and deleting + useEffect(() => { + if (!database || databaseStatus === "loading") { + return; + } + + function handleMapChanges(changes) { + for (let change of changes) { + if (change.table === "tokens") { + if (change.type === 1) { + // Created + const token = change.obj; + setTokens((prevTokens) => [token, ...prevTokens]); + } else if (change.type === 3) { + // Deleted + const id = change.key; + setTokens((prevTokens) => { + const filtered = prevTokens.filter((token) => token.id !== id); + return filtered; + }); + } + } + } + } + + database.on("changes", handleMapChanges); + + return () => { + database.on("changes").unsubscribe(handleMapChanges); + }; + }, [database, databaseStatus]); + const ownedTokens = tokens.filter((token) => token.owner === userId); const tokensById = tokens.reduce((obj, token) => { diff --git a/src/database.js b/src/database.js index 9a8b84d..7a9c4d6 100644 --- a/src/database.js +++ b/src/database.js @@ -1,5 +1,6 @@ // eslint-disable-next-line no-unused-vars import Dexie, { Version, DexieOptions } from "dexie"; +import "dexie-observable"; import blobToBuffer from "./helpers/blobToBuffer"; import { getGridDefaultInset } from "./helpers/grid"; @@ -391,9 +392,13 @@ const versions = { }); }); }, + // 1.8.0 - Upgrade for Dexie.Observable + 21(v) { + v.stores({}); + }, }; -const latestVersion = 20; +const latestVersion = 21; /** * Load versions onto a database up to a specific version number diff --git a/src/modals/EditMapModal.js b/src/modals/EditMapModal.js index a89ade8..ad38c8c 100644 --- a/src/modals/EditMapModal.js +++ b/src/modals/EditMapModal.js @@ -147,7 +147,7 @@ function EditMapModal({ isOpen, onDone, mapId }) { - {isLoading ? ( + {isLoading || !map ? ( Edit token - {isLoading ? ( + {isLoading || !token ? (