diff --git a/package.json b/package.json index 562ab0b..c474470 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "color": "^3.1.3", "comlink": "^4.3.0", "deep-diff": "^1.0.2", - "dexie": "^3.0.3", - "dexie-observable": "^3.0.0-beta.10", + "dexie": "3.1.0-beta.13", + "dexie-react-hooks": "^1.0.6", "err-code": "^3.0.1", "fake-indexeddb": "^3.1.2", "file-saver": "^2.0.5", diff --git a/src/contexts/AssetsContext.js b/src/contexts/AssetsContext.js index c1828a5..46cb63d 100644 --- a/src/contexts/AssetsContext.js +++ b/src/contexts/AssetsContext.js @@ -236,38 +236,7 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) { updateAssetURL(); - // Update the url when the asset is added to the db after the hook is used - function handleAssetChanges(changes) { - for (let change of changes) { - const id = change.key; - if ( - change.table === "assets" && - id === assetId && - (change.type === 1 || change.type === 2) - ) { - const asset = change.obj; - setAssetURLs((prevURLs) => { - if (!(assetId in prevURLs)) { - const url = URL.createObjectURL( - new Blob([asset.file], { type: asset.mime }) - ); - return { - ...prevURLs, - [assetId]: { url, id: assetId, references: 1 }, - }; - } else { - return prevURLs; - } - }); - } - } - } - - database.on("changes", handleAssetChanges); - return () => { - database.on("changes").unsubscribe(handleAssetChanges); - // Decrease references setAssetURLs((prevURLs) => { if (assetId in prevURLs) { diff --git a/src/contexts/MapDataContext.js b/src/contexts/MapDataContext.js index ebf95bc..f33cf98 100644 --- a/src/contexts/MapDataContext.js +++ b/src/contexts/MapDataContext.js @@ -1,9 +1,14 @@ -import React, { useEffect, useState, useContext, useCallback } from "react"; +import React, { + useEffect, + useState, + useContext, + useCallback, + useMemo, +} from "react"; +import { useLiveQuery } from "dexie-react-hooks"; -import { useUserId } from "./UserIdContext"; import { useDatabase } from "./DatabaseContext"; -import { applyObservableChange } from "../helpers/dexie"; import { removeGroupsItems } from "../helpers/group"; const MapDataContext = React.createContext(); @@ -18,33 +23,39 @@ const defaultMapState = { }; export function MapDataProvider({ children }) { - const { database, databaseStatus } = useDatabase(); - const userId = useUserId(); + const { database } = useDatabase(); + + const mapsQuery = useLiveQuery( + () => database?.table("maps").toArray(), + [database] + ); + const mapStatesQuery = useLiveQuery( + () => database?.table("states").toArray(), + [database] + ); + + const maps = useMemo(() => mapsQuery || [], [mapsQuery]); + const mapStates = useMemo(() => mapStatesQuery || [], [mapStatesQuery]); + const mapsLoading = useMemo( + () => !mapsQuery || !mapStatesQuery, + [mapsQuery, mapStatesQuery] + ); + + const mapGroupQuery = useLiveQuery( + () => database?.table("groups").get("maps"), + [database] + ); - const [maps, setMaps] = useState([]); - const [mapStates, setMapStates] = useState([]); - const [mapsLoading, setMapsLoading] = useState(true); const [mapGroups, setMapGroups] = useState([]); - - // Load maps from the database and ensure state is properly setup useEffect(() => { - if (!userId || !database || databaseStatus === "loading") { - return; - } - - async function loadMaps() { - const storedMaps = await database.table("maps").toArray(); - setMaps(storedMaps); - const storedStates = await database.table("states").toArray(); - setMapStates(storedStates); + async function updateMapGroups() { const group = await database.table("groups").get("maps"); - const storedGroups = group.items; - setMapGroups(storedGroups); - setMapsLoading(false); + setMapGroups(group.items); } - - loadMaps(); - }, [userId, database, databaseStatus]); + if (database && mapGroupQuery) { + updateMapGroups(); + } + }, [mapGroupQuery, database]); const getMap = useCallback( async (mapId) => { @@ -138,77 +149,6 @@ export function MapDataProvider({ children }) { [database] ); - // 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 === 2) { - const map = change.obj; - setMaps((prevMaps) => { - const newMaps = [...prevMaps]; - const i = newMaps.findIndex((m) => m.id === map.id); - if (i > -1) { - newMaps[i] = map; - } - return newMaps; - }); - } 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; - }); - } - } - if (change.table === "states") { - if (change.type === 2) { - // Update map state - const state = change.obj; - setMapStates((prevMapStates) => { - const newStates = [...prevMapStates]; - const i = newStates.findIndex((s) => s.mapId === state.mapId); - if (i > -1) { - newStates[i] = state; - } - return newStates; - }); - } - } - if (change.table === "groups") { - if (change.type === 2 && change.key === "maps") { - const group = applyObservableChange(change); - const groups = group.items.filter((item) => item !== null); - setMapGroups(groups); - } - } - } - } - - database.on("changes", handleMapChanges); - - return () => { - database.on("changes").unsubscribe(handleMapChanges); - }; - }, [database, databaseStatus]); - const [mapsById, setMapsById] = useState({}); useEffect(() => { setMapsById( diff --git a/src/contexts/TokenDataContext.js b/src/contexts/TokenDataContext.js index b4bd33c..a805ac5 100644 --- a/src/contexts/TokenDataContext.js +++ b/src/contexts/TokenDataContext.js @@ -1,37 +1,44 @@ -import React, { useEffect, useState, useContext, useCallback } from "react"; +import React, { + useEffect, + useState, + useContext, + useCallback, + useMemo, +} from "react"; +import { useLiveQuery } from "dexie-react-hooks"; -import { useUserId } from "./UserIdContext"; import { useDatabase } from "./DatabaseContext"; -import { applyObservableChange } from "../helpers/dexie"; import { removeGroupsItems } from "../helpers/group"; const TokenDataContext = React.createContext(); export function TokenDataProvider({ children }) { - const { database, databaseStatus } = useDatabase(); - const userId = useUserId(); + const { database } = useDatabase(); + + const tokensQuery = useLiveQuery( + () => database?.table("tokens").toArray(), + [database] + ); + + const tokens = useMemo(() => tokensQuery || [], [tokensQuery]); + const tokensLoading = useMemo(() => !tokensQuery, [tokensQuery]); + + const tokenGroupQuery = useLiveQuery( + () => database?.table("groups").get("tokens"), + [database] + ); - const [tokens, setTokens] = useState([]); - const [tokensLoading, setTokensLoading] = useState(true); const [tokenGroups, setTokenGroups] = useState([]); - useEffect(() => { - if (!userId || !database || databaseStatus === "loading") { - return; - } - - async function loadTokens() { - const storedTokens = await database.table("tokens").toArray(); - setTokens(storedTokens); + async function updateTokenGroups() { const group = await database.table("groups").get("tokens"); - const storedGroups = group.items; - setTokenGroups(storedGroups); - setTokensLoading(false); + setTokenGroups(group.items); } - - loadTokens(); - }, [userId, database, databaseStatus]); + if (database && tokenGroupQuery) { + updateTokenGroups(); + } + }, [tokenGroupQuery, database]); const getToken = useCallback( async (tokenId) => { @@ -83,15 +90,15 @@ export function TokenDataProvider({ children }) { const updateTokensHidden = useCallback( async (ids, hideInSidebar) => { - // Update immediately to avoid UI delay - setTokens((prevTokens) => { - let newTokens = [...prevTokens]; - for (let id of ids) { - const tokenIndex = newTokens.findIndex((token) => token.id === id); - newTokens[tokenIndex].hideInSidebar = hideInSidebar; - } - return newTokens; - }); + // // Update immediately to avoid UI delay + // setTokens((prevTokens) => { + // let newTokens = [...prevTokens]; + // for (let id of ids) { + // const tokenIndex = newTokens.findIndex((token) => token.id === id); + // newTokens[tokenIndex].hideInSidebar = hideInSidebar; + // } + // return newTokens; + // }); await Promise.all( ids.map((id) => database.table("tokens").update(id, { hideInSidebar })) ); @@ -108,67 +115,6 @@ export function TokenDataProvider({ children }) { [database] ); - // Create DB observable to sync creating and deleting - useEffect(() => { - if (!database || databaseStatus === "loading") { - return; - } - - function handleTokenChanges(changes) { - // Pool token changes together to call a single state update at the end - let tokensCreated = []; - let tokensUpdated = {}; - let tokensDeleted = []; - for (let change of changes) { - if (change.table === "tokens") { - if (change.type === 1) { - // Created - const token = change.obj; - tokensCreated.push(token); - } else if (change.type === 2) { - // Updated - const token = change.obj; - tokensUpdated[token.id] = token; - } else if (change.type === 3) { - // Deleted - const id = change.key; - tokensDeleted.push(id); - } - } - if (change.table === "groups") { - if (change.type === 2 && change.key === "tokens") { - const group = applyObservableChange(change); - const groups = group.items.filter((item) => item !== null); - setTokenGroups(groups); - } - } - } - const tokensUpdatedArray = Object.values(tokensUpdated); - if ( - tokensCreated.length > 0 || - tokensUpdatedArray.length > 0 || - tokensDeleted.length > 0 - ) { - setTokens((prevTokens) => { - let newTokens = [...tokensCreated, ...prevTokens]; - for (let token of tokensUpdatedArray) { - const tokenIndex = newTokens.findIndex((t) => t.id === token.id); - if (tokenIndex > -1) { - newTokens[tokenIndex] = token; - } - } - return newTokens.filter((token) => !tokensDeleted.includes(token.id)); - }); - } - } - - database.on("changes", handleTokenChanges); - - return () => { - database.on("changes").unsubscribe(handleTokenChanges); - }; - }, [database, databaseStatus]); - const [tokensById, setTokensById] = useState({}); useEffect(() => { setTokensById( diff --git a/src/database.js b/src/database.js index a5251f1..c834c95 100644 --- a/src/database.js +++ b/src/database.js @@ -1,7 +1,6 @@ // eslint-disable-next-line no-unused-vars import Dexie, { DexieOptions } from "dexie"; import { v4 as uuid } from "uuid"; -import "dexie-observable"; import { loadVersions } from "./upgrade"; import { getDefaultMaps } from "./maps"; diff --git a/yarn.lock b/yarn.lock index 9907962..0b96658 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5369,12 +5369,17 @@ detect-port-alt@1.1.6: address "^1.0.1" debug "^2.6.0" -dexie-observable@^3.0.0-beta.10: - version "3.0.0-beta.10" - resolved "https://registry.yarnpkg.com/dexie-observable/-/dexie-observable-3.0.0-beta.10.tgz#ad7a7e136defbb62f9eab9198a5cb9e10bce1c87" - integrity sha512-GMPwQMLh1nYqM1MYsOZudsIwSMqDMrAOBxNuw+Y2ijsrQTBPi3nRF2CinY02IdlmffkaU7DsDfnlgdaMEaiHTQ== +dexie-react-hooks@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/dexie-react-hooks/-/dexie-react-hooks-1.0.6.tgz#a21d116addb2bb785507bf924cc6e963d858d5d5" + integrity sha512-OFoOBC4BQzkVGicuWl/cIMtlPp0wTAnUXwUJzq+l/zp0XVGmwEWkemRFq7JbudJLT0DINFVVzgVhGV7KOUK7uA== -"dexie@^3.0.0-alpha.5 || ^2.0.4", dexie@^3.0.3: +dexie@3.1.0-beta.13: + version "3.1.0-beta.13" + resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.1.0-beta.13.tgz#54b3438e2aca3b60f87a823a535ce1b4313056ec" + integrity sha512-pUcX9YyX1VDjF1oMqiOys6N2zoXIA/CeTghB3P4Ee77U8n9q0qa2pmNYoHyyYPKLU58+gzsMJuOc6HLPJDQrQQ== + +"dexie@^3.0.0-alpha.5 || ^2.0.4": version "3.0.3" resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.0.3.tgz#ede63849dfe5f07e13e99bb72a040e8ac1d29dab" integrity sha512-BSFhGpngnCl1DOr+8YNwBDobRMH0ziJs2vts69VilwetHYOtEDcLqo7d/XiIphM0tJZ2rPPyAGd31lgH2Ln3nw== @@ -7202,7 +7207,7 @@ image-outline@^0.1.0: marching-squares "^0.2.0" minimist "^1.2.0" ndarray "^1.0.18" - + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"