diff --git a/src/App.js b/src/App.js index ee51563..99058b5 100644 --- a/src/App.js +++ b/src/App.js @@ -12,51 +12,54 @@ import HowTo from "./routes/HowTo"; import Donate from "./routes/Donate"; import { AuthProvider } from "./contexts/AuthContext"; -import { DatabaseProvider } from "./contexts/DatabaseContext"; import { SettingsProvider } from "./contexts/SettingsContext"; import { KeyboardProvider } from "./contexts/KeyboardContext"; +import { DatabaseProvider } from "./contexts/DatabaseContext"; +import { UserIdProvider } from "./contexts/UserIdContext"; import { ToastProvider } from "./components/Toast"; function App() { return ( - - - - - - - - - - - {/* Legacy support camel case routes */} - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + {/* Legacy support camel case routes */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/components/UpgradingLoadingOverlay.js b/src/components/UpgradingLoadingOverlay.js new file mode 100644 index 0000000..c284cbb --- /dev/null +++ b/src/components/UpgradingLoadingOverlay.js @@ -0,0 +1,80 @@ +import React, { useState, useEffect } from "react"; +import { Text } from "theme-ui"; + +import LoadingOverlay from "./LoadingOverlay"; + +import { shuffle } from "../helpers/shared"; + +const facts = [ + "Owls can rotate their necks 270 degrees", + "Not all owls hoot", + "Owl flight is almost completely silent", + "Owls are used to represent the Goddess Athena in Greek mythology", + "Owls have the best night vision of any animal", + "Bears can run up to 40 mi (~64 km) per hour ", + "A hibernating bear’s heart beats at 8 bpm", + "Bears can see in colour", + "Koala bears are not bears", + "A polar bear can swim up to 100 mi (~161 km) without resting", + "A group of bears is called a sleuth or sloth", + "Not all bears hibernate", +]; + +function UpgradingLoadingOverlay() { + const [subText, setSubText] = useState(); + + useEffect(() => { + let index = 0; + let randomFacts = shuffle(facts); + + function updateFact() { + setSubText(randomFacts[index % (randomFacts.length - 1)]); + index += 1; + } + + // Show first fact after 10 seconds then every 20 seconds after that + let interval; + let timeout = setTimeout(() => { + updateFact(); + interval = setInterval(() => { + updateFact(); + }, 20 * 1000); + }, 10 * 1000); + + return () => { + clearTimeout(timeout); + if (interval) { + clearInterval(interval); + } + }; + }, []); + + return ( + + + Database upgrading, please wait... + + {subText && ( + <> + + We're still working on the upgrade. In the meantime, did you know? + + + {subText} + + + )} + + ); +} + +export default UpgradingLoadingOverlay; diff --git a/src/components/image/GlobalImageDrop.js b/src/components/image/GlobalImageDrop.js index 10efcd6..f36ad2f 100644 --- a/src/components/image/GlobalImageDrop.js +++ b/src/components/image/GlobalImageDrop.js @@ -14,7 +14,7 @@ import { } from "../../helpers/token"; import Vector2 from "../../helpers/Vector2"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; import { useMapData } from "../../contexts/MapDataContext"; import { useTokenData } from "../../contexts/TokenDataContext"; import { useAssets } from "../../contexts/AssetsContext"; @@ -25,7 +25,7 @@ import useImageDrop from "../../hooks/useImageDrop"; function GlobalImageDrop({ children, onMapChange, onMapTokensStateCreate }) { const { addToast } = useToasts(); - const { userId } = useAuth(); + const userId = useUserId(); const { addMap, getMapState } = useMapData(); const { addToken } = useTokenData(); const { addAssets } = useAssets(); diff --git a/src/components/map/MapNotes.js b/src/components/map/MapNotes.js index c2706c8..d28f364 100644 --- a/src/components/map/MapNotes.js +++ b/src/components/map/MapNotes.js @@ -4,7 +4,7 @@ import { Group } from "react-konva"; import { useInteractionEmitter } from "../../contexts/MapInteractionContext"; import { useMapStage } from "../../contexts/MapStageContext"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; import Vector2 from "../../helpers/Vector2"; import { getRelativePointerPosition } from "../../helpers/konva"; @@ -28,7 +28,7 @@ function MapNotes({ fadeOnHover, }) { const interactionEmitter = useInteractionEmitter(); - const { userId } = useAuth(); + const userId = useUserId(); const mapStageRef = useMapStage(); const [isBrushDown, setIsBrushDown] = useState(false); const [noteData, setNoteData] = useState(null); diff --git a/src/components/map/MapToken.js b/src/components/map/MapToken.js index 8ceb93a..9a4d6a5 100644 --- a/src/components/map/MapToken.js +++ b/src/components/map/MapToken.js @@ -6,7 +6,7 @@ import useImage from "use-image"; import usePrevious from "../../hooks/usePrevious"; import useGridSnapping from "../../hooks/useGridSnapping"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; import { useSetPreventMapInteraction, useMapWidth, @@ -33,7 +33,7 @@ function MapToken({ fadeOnHover, map, }) { - const { userId } = useAuth(); + const userId = useUserId(); const mapWidth = useMapWidth(); const mapHeight = useMapHeight(); diff --git a/src/components/map/SelectMapButton.js b/src/components/map/SelectMapButton.js index d5f7d7f..18c1654 100644 --- a/src/components/map/SelectMapButton.js +++ b/src/components/map/SelectMapButton.js @@ -5,7 +5,7 @@ import SelectMapModal from "../../modals/SelectMapModal"; import SelectMapIcon from "../../icons/SelectMapIcon"; import { useMapData } from "../../contexts/MapDataContext"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; function SelectMapButton({ onMapChange, @@ -17,7 +17,7 @@ function SelectMapButton({ const [isModalOpen, setIsModalOpen] = useState(false); const { updateMapState } = useMapData(); - const { userId } = useAuth(); + const userId = useUserId(); function openModal() { if (currentMapState && currentMap && currentMap.owner === userId) { updateMapState(currentMapState.mapId, currentMapState); diff --git a/src/components/note/Note.js b/src/components/note/Note.js index 343be01..a5cd5af 100644 --- a/src/components/note/Note.js +++ b/src/components/note/Note.js @@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from "react"; import { Rect, Text } from "react-konva"; import { useSpring, animated } from "react-spring/konva"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; import { useSetPreventMapInteraction, useMapWidth, @@ -27,7 +27,7 @@ function Note({ onNoteDragEnd, fadeOnHover, }) { - const { userId } = useAuth(); + const userId = useUserId(); const mapWidth = useMapWidth(); const mapHeight = useMapHeight(); diff --git a/src/components/note/NoteMenu.js b/src/components/note/NoteMenu.js index 17c272f..8060fd8 100644 --- a/src/components/note/NoteMenu.js +++ b/src/components/note/NoteMenu.js @@ -17,7 +17,7 @@ import HideIcon from "../../icons/TokenHideIcon"; import NoteIcon from "../../icons/NoteToolIcon"; import TextIcon from "../../icons/NoteTextIcon"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; const defaultNoteMaxSize = 6; @@ -29,7 +29,7 @@ function NoteMenu({ onNoteChange, map, }) { - const { userId } = useAuth(); + const userId = useUserId(); const wasOpen = usePrevious(isOpen); diff --git a/src/components/token/TokenBar.js b/src/components/token/TokenBar.js index d59e221..e01013e 100644 --- a/src/components/token/TokenBar.js +++ b/src/components/token/TokenBar.js @@ -21,7 +21,7 @@ import useSetting from "../../hooks/useSetting"; import usePreventSelect from "../../hooks/usePreventSelect"; import { useTokenData } from "../../contexts/TokenDataContext"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; import { useMapStage } from "../../contexts/MapStageContext"; import DragContext from "../../contexts/DragContext"; @@ -33,7 +33,7 @@ import { findGroup } from "../../helpers/group"; import Vector2 from "../../helpers/Vector2"; function TokenBar({ onMapTokensStateCreate }) { - const { userId } = useAuth(); + const userId = useUserId(); const { tokensById, tokenGroups } = useTokenData(); const [fullScreen] = useSetting("map.fullScreen"); diff --git a/src/components/token/TokenDragOverlay.js b/src/components/token/TokenDragOverlay.js index 731ae16..f484a0c 100644 --- a/src/components/token/TokenDragOverlay.js +++ b/src/components/token/TokenDragOverlay.js @@ -1,6 +1,6 @@ import React from "react"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; import { useMapWidth, useMapHeight, @@ -16,7 +16,7 @@ function TokenDragOverlay({ tokenGroup, dragging, }) { - const { userId } = useAuth(); + const userId = useUserId(); const mapWidth = useMapWidth(); const mapHeight = useMapHeight(); diff --git a/src/components/token/TokenMenu.js b/src/components/token/TokenMenu.js index 2fb6eac..03ce419 100644 --- a/src/components/token/TokenMenu.js +++ b/src/components/token/TokenMenu.js @@ -14,7 +14,7 @@ import UnlockIcon from "../../icons/TokenUnlockIcon"; import ShowIcon from "../../icons/TokenShowIcon"; import HideIcon from "../../icons/TokenHideIcon"; -import { useAuth } from "../../contexts/AuthContext"; +import { useUserId } from "../../contexts/UserIdContext"; const defaultTokenMaxSize = 6; function TokenMenu({ @@ -25,7 +25,7 @@ function TokenMenu({ onTokenStateChange, map, }) { - const { userId } = useAuth(); + const userId = useUserId(); const wasOpen = usePrevious(isOpen); diff --git a/src/contexts/AssetsContext.js b/src/contexts/AssetsContext.js index c327f0e..c1828a5 100644 --- a/src/contexts/AssetsContext.js +++ b/src/contexts/AssetsContext.js @@ -50,11 +50,13 @@ const AssetsContext = React.createContext(); const maxCacheSize = 1e8; export function AssetsProvider({ children }) { - const { worker, database } = useDatabase(); + const { worker, database, databaseStatus } = useDatabase(); useEffect(() => { - worker.cleanAssetCache(maxCacheSize); - }, [worker]); + if (databaseStatus === "loaded") { + worker.cleanAssetCache(maxCacheSize); + } + }, [worker, databaseStatus]); const getAsset = useCallback( async (assetId) => { diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js index f08971c..cd0b4e9 100644 --- a/src/contexts/AuthContext.js +++ b/src/contexts/AuthContext.js @@ -1,7 +1,5 @@ import React, { useState, useEffect, useContext } from "react"; -import { useDatabase } from "./DatabaseContext"; - import FakeStorage from "../helpers/FakeStorage"; const AuthContext = React.createContext(); @@ -17,31 +15,13 @@ try { } export function AuthProvider({ children }) { - const { database, databaseStatus } = useDatabase(); - const [password, setPassword] = useState(storage.getItem("auth") || ""); useEffect(() => { storage.setItem("auth", password); }, [password]); - const [userId, setUserId] = useState(); - useEffect(() => { - if (!database || databaseStatus === "loading") { - return; - } - async function loadUserId() { - const storedUserId = await database.table("user").get("userId"); - if (storedUserId) { - setUserId(storedUserId.value); - } - } - - loadUserId(); - }, [database, databaseStatus]); - const value = { - userId, password, setPassword, }; diff --git a/src/contexts/DatabaseContext.js b/src/contexts/DatabaseContext.js index fd5cfb3..64714ee 100644 --- a/src/contexts/DatabaseContext.js +++ b/src/contexts/DatabaseContext.js @@ -26,6 +26,7 @@ const worker = Comlink.wrap(new DatabaseWorker()); export function DatabaseProvider({ children }) { const [database, setDatabase] = useState(); + // "loading" | "disabled" | "upgrading" | "loaded" const [databaseStatus, setDatabaseStatus] = useState("loading"); const [databaseError, setDatabaseError] = useState(); @@ -34,7 +35,15 @@ export function DatabaseProvider({ children }) { let testDBRequest = window.indexedDB.open("__test"); testDBRequest.onsuccess = async function () { testDBRequest.result.close(); - let db = getDatabase({ autoOpen: false }); + let db = getDatabase( + { autoOpen: false }, + undefined, + undefined, + true, + (v) => { + setDatabaseStatus("upgrading"); + } + ); setDatabase(db); db.on("ready", () => { setDatabaseStatus("loaded"); diff --git a/src/contexts/MapDataContext.js b/src/contexts/MapDataContext.js index 9b4e636..ebf95bc 100644 --- a/src/contexts/MapDataContext.js +++ b/src/contexts/MapDataContext.js @@ -1,6 +1,6 @@ import React, { useEffect, useState, useContext, useCallback } from "react"; -import { useAuth } from "./AuthContext"; +import { useUserId } from "./UserIdContext"; import { useDatabase } from "./DatabaseContext"; import { applyObservableChange } from "../helpers/dexie"; @@ -19,7 +19,7 @@ const defaultMapState = { export function MapDataProvider({ children }) { const { database, databaseStatus } = useDatabase(); - const { userId } = useAuth(); + const userId = useUserId(); const [maps, setMaps] = useState([]); const [mapStates, setMapStates] = useState([]); diff --git a/src/contexts/PlayerContext.js b/src/contexts/PlayerContext.js index 26cd95c..ca90483 100644 --- a/src/contexts/PlayerContext.js +++ b/src/contexts/PlayerContext.js @@ -1,7 +1,7 @@ import React, { useEffect, useContext } from "react"; import { useDatabase } from "./DatabaseContext"; -import { useAuth } from "./AuthContext"; +import { useUserId } from "./UserIdContext"; import { getRandomMonster } from "../helpers/monsters"; @@ -11,7 +11,7 @@ export const PlayerStateContext = React.createContext(); export const PlayerUpdaterContext = React.createContext(() => {}); export function PlayerProvider({ session, children }) { - const { userId } = useAuth(); + const userId = useUserId(); const { database, databaseStatus } = useDatabase(); const [playerState, setPlayerState] = useNetworkedState( @@ -53,7 +53,7 @@ export function PlayerProvider({ session, children }) { if ( playerState.nickname && database !== undefined && - databaseStatus !== "loading" + (databaseStatus === "loaded" || databaseStatus === "disabled") ) { database .table("user") diff --git a/src/contexts/TokenDataContext.js b/src/contexts/TokenDataContext.js index 72780f3..b4bd33c 100644 --- a/src/contexts/TokenDataContext.js +++ b/src/contexts/TokenDataContext.js @@ -1,6 +1,6 @@ import React, { useEffect, useState, useContext, useCallback } from "react"; -import { useAuth } from "./AuthContext"; +import { useUserId } from "./UserIdContext"; import { useDatabase } from "./DatabaseContext"; import { applyObservableChange } from "../helpers/dexie"; @@ -10,7 +10,7 @@ const TokenDataContext = React.createContext(); export function TokenDataProvider({ children }) { const { database, databaseStatus } = useDatabase(); - const { userId } = useAuth(); + const userId = useUserId(); const [tokens, setTokens] = useState([]); const [tokensLoading, setTokensLoading] = useState(true); diff --git a/src/contexts/UserIdContext.js b/src/contexts/UserIdContext.js new file mode 100644 index 0000000..72d3bb8 --- /dev/null +++ b/src/contexts/UserIdContext.js @@ -0,0 +1,36 @@ +import React, { useEffect, useState, useContext } from "react"; + +import { useDatabase } from "./DatabaseContext"; +/** + * @type {React.Context} + */ +const UserIdContext = React.createContext(); + +export function UserIdProvider({ children }) { + const { database, databaseStatus } = useDatabase(); + + const [userId, setUserId] = useState(); + useEffect(() => { + if (!database || databaseStatus === "loading") { + return; + } + async function loadUserId() { + const storedUserId = await database.table("user").get("userId"); + if (storedUserId) { + setUserId(storedUserId.value); + } + } + + loadUserId(); + }, [database, databaseStatus]); + + return ( + {children} + ); +} + +export function useUserId() { + return useContext(UserIdContext); +} + +export default UserIdContext; diff --git a/src/database.js b/src/database.js index fc70dfb..a5251f1 100644 --- a/src/database.js +++ b/src/database.js @@ -3,7 +3,7 @@ import Dexie, { DexieOptions } from "dexie"; import { v4 as uuid } from "uuid"; import "dexie-observable"; -import { loadVersions, latestVersion } from "./upgrade"; +import { loadVersions } from "./upgrade"; import { getDefaultMaps } from "./maps"; import { getDefaultTokens } from "./tokens"; @@ -36,16 +36,18 @@ function populate(db) { * @param {string=} name * @param {number=} versionNumber * @param {boolean=} populateData + * @param {import("./upgrade").OnUpgrade=} onUpgrade * @returns {Dexie} */ export function getDatabase( options, name = "OwlbearRodeoDB", - versionNumber = latestVersion, - populateData = true + versionNumber = undefined, + populateData = true, + onUpgrade = undefined ) { let db = new Dexie(name, options); - loadVersions(db, versionNumber); + loadVersions(db, versionNumber, onUpgrade); if (populateData) { populate(db); } diff --git a/src/helpers/KonvaBridge.js b/src/helpers/KonvaBridge.js index 935627f..c71e9a4 100644 --- a/src/helpers/KonvaBridge.js +++ b/src/helpers/KonvaBridge.js @@ -19,7 +19,7 @@ import { useDebouncedStageScale, } from "../contexts/MapInteractionContext"; import { MapStageProvider, useMapStage } from "../contexts/MapStageContext"; -import AuthContext, { useAuth } from "../contexts/AuthContext"; +import UserIdContext, { useUserId } from "../contexts/UserIdContext"; import SettingsContext, { useSettings } from "../contexts/SettingsContext"; import KeyboardContext from "../contexts/KeyboardContext"; import AssetsContext, { @@ -50,7 +50,7 @@ import DatabaseContext, { useDatabase } from "../contexts/DatabaseContext"; */ function KonvaBridge({ stageRender, children }) { const mapStageRef = useMapStage(); - const auth = useAuth(); + const userId = useUserId(); const settings = useSettings(); const assets = useAssets(); const assetURLs = useContext(AssetURLsStateContext); @@ -78,7 +78,7 @@ function KonvaBridge({ stageRender, children }) { return stageRender( - + @@ -140,7 +140,7 @@ function KonvaBridge({ stageRender, children }) { - + ); } diff --git a/src/modals/ImportExportModal.js b/src/modals/ImportExportModal.js index 2d24255..b0f92e4 100644 --- a/src/modals/ImportExportModal.js +++ b/src/modals/ImportExportModal.js @@ -11,7 +11,7 @@ import LoadingOverlay from "../components/LoadingOverlay"; import LoadingBar from "../components/LoadingBar"; import ErrorBanner from "../components/banner/ErrorBanner"; -import { useAuth } from "../contexts/AuthContext"; +import { useUserId } from "../contexts/UserIdContext"; import { useDatabase } from "../contexts/DatabaseContext"; import SelectDataModal from "./SelectDataModal"; @@ -22,7 +22,7 @@ const importDBName = "OwlbearRodeoImportDB"; function ImportExportModal({ isOpen, onRequestClose }) { const { worker } = useDatabase(); - const { userId } = useAuth(); + const userId = useUserId(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); diff --git a/src/modals/SelectMapModal.js b/src/modals/SelectMapModal.js index be7dfb0..46a69a1 100644 --- a/src/modals/SelectMapModal.js +++ b/src/modals/SelectMapModal.js @@ -25,7 +25,7 @@ import { createMapFromFile } from "../helpers/map"; import useResponsiveLayout from "../hooks/useResponsiveLayout"; import { useMapData } from "../contexts/MapDataContext"; -import { useAuth } from "../contexts/AuthContext"; +import { useUserId } from "../contexts/UserIdContext"; import { useAssets } from "../contexts/AssetsContext"; import { GroupProvider } from "../contexts/GroupContext"; import { TileDragProvider } from "../contexts/TileDragContext"; @@ -40,7 +40,7 @@ function SelectMapModal({ }) { const { addToast } = useToasts(); - const { userId } = useAuth(); + const userId = useUserId(); const { maps, mapStates, diff --git a/src/modals/SelectTokensModal.js b/src/modals/SelectTokensModal.js index 734f352..769adc6 100644 --- a/src/modals/SelectTokensModal.js +++ b/src/modals/SelectTokensModal.js @@ -29,7 +29,7 @@ import Vector2 from "../helpers/Vector2"; import useResponsiveLayout from "../hooks/useResponsiveLayout"; import { useTokenData } from "../contexts/TokenDataContext"; -import { useAuth } from "../contexts/AuthContext"; +import { useUserId } from "../contexts/UserIdContext"; import { useAssets } from "../contexts/AssetsContext"; import { GroupProvider } from "../contexts/GroupContext"; import { TileDragProvider } from "../contexts/TileDragContext"; @@ -38,7 +38,7 @@ import { useMapStage } from "../contexts/MapStageContext"; function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) { const { addToast } = useToasts(); - const { userId } = useAuth(); + const userId = useUserId(); const { tokens, addToken, diff --git a/src/modals/SettingsModal.js b/src/modals/SettingsModal.js index 21fd1a8..9ca0f9c 100644 --- a/src/modals/SettingsModal.js +++ b/src/modals/SettingsModal.js @@ -14,7 +14,7 @@ import Modal from "../components/Modal"; import Slider from "../components/Slider"; import LoadingOverlay from "../components/LoadingOverlay"; -import { useAuth } from "../contexts/AuthContext"; +import { useUserId } from "../contexts/UserIdContext"; import { useDatabase } from "../contexts/DatabaseContext"; import useSetting from "../hooks/useSetting"; @@ -24,7 +24,7 @@ import ImportExportModal from "./ImportExportModal"; function SettingsModal({ isOpen, onRequestClose }) { const { database, databaseStatus } = useDatabase(); - const { userId } = useAuth(); + const userId = useUserId(); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [labelSize, setLabelSize] = useSetting("map.labelSize"); const [gridSnappingSensitivity, setGridSnappingSensitivity] = useSetting( diff --git a/src/network/NetworkedMapAndTokens.js b/src/network/NetworkedMapAndTokens.js index fd51e7c..9caf9b3 100644 --- a/src/network/NetworkedMapAndTokens.js +++ b/src/network/NetworkedMapAndTokens.js @@ -3,7 +3,7 @@ import { useToasts } from "react-toast-notifications"; import { useMapData } from "../contexts/MapDataContext"; import { useMapLoading } from "../contexts/MapLoadingContext"; -import { useAuth } from "../contexts/AuthContext"; +import { useUserId } from "../contexts/UserIdContext"; import { useDatabase } from "../contexts/DatabaseContext"; import { useParty } from "../contexts/PartyContext"; import { useAssets } from "../contexts/AssetsContext"; @@ -39,7 +39,7 @@ const defaultMapActions = { */ function NetworkedMapAndTokens({ session }) { const { addToast } = useToasts(); - const { userId } = useAuth(); + const userId = useUserId(); const partyState = useParty(); const { assetLoadStart, assetProgressUpdate, isLoading } = useMapLoading(); diff --git a/src/network/NetworkedMapPointer.js b/src/network/NetworkedMapPointer.js index 6398eb1..87b5437 100644 --- a/src/network/NetworkedMapPointer.js +++ b/src/network/NetworkedMapPointer.js @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from "react"; import { Group } from "react-konva"; -import { useAuth } from "../contexts/AuthContext"; +import { useUserId } from "../contexts/UserIdContext"; import MapPointer from "../components/map/MapPointer"; import { isEmpty } from "../helpers/shared"; @@ -13,7 +13,7 @@ import useSetting from "../hooks/useSetting"; const sendTickRate = 50; function NetworkedMapPointer({ session, active }) { - const { userId } = useAuth(); + const userId = useUserId(); const [localPointerState, setLocalPointerState] = useState({}); const [pointerColor] = useSetting("pointer.color"); diff --git a/src/routes/Game.js b/src/routes/Game.js index 2e06da5..78043b3 100644 --- a/src/routes/Game.js +++ b/src/routes/Game.js @@ -8,6 +8,7 @@ import OfflineBanner from "../components/banner/OfflineBanner"; import LoadingOverlay from "../components/LoadingOverlay"; import Link from "../components/Link"; import MapLoadingOverlay from "../components/map/MapLoadingOverlay"; +import UpgradingLoadingOverlay from "../components/UpgradingLoadingOverlay"; import AuthModal from "../modals/AuthModal"; import GameExpiredModal from "../modals/GameExpiredModal"; @@ -90,13 +91,16 @@ function Game() { // Join game useEffect(() => { - if (sessionStatus === "ready" && databaseStatus !== "loading") { + if ( + sessionStatus === "ready" && + (databaseStatus === "loaded" || databaseStatus === "disabled") + ) { session.joinGame(gameId, password); } }, [gameId, password, databaseStatus, session, sessionStatus]); function handleAuthSubmit(newPassword) { - if (databaseStatus !== "loading") { + if (databaseStatus === "loaded" || databaseStatus === "disabled") { session.joinGame(gameId, newPassword); } } @@ -151,6 +155,9 @@ function Game() { isOpen={sessionStatus === "needs_update"} /> {!sessionStatus && } + {sessionStatus && databaseStatus === "upgrading" && ( + + )} diff --git a/src/upgrade.js b/src/upgrade.js index 0cddfd9..f7d3b99 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -17,9 +17,15 @@ import { import { getDefaultMaps } from "./maps"; import { getDefaultTokens } from "./tokens"; +/** + * @callback OnUpgrade + * @param {number} versionNumber + */ + /** * @callback VersionCallback * @param {Version} version + * @param {OnUpgrade=} onUpgrade */ /** @@ -37,8 +43,9 @@ export const versions = { }); }, // v1.2.1 - Move from blob files to array buffers - 2(v) { + 2(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(2); const maps = await Dexie.waitFor(tx.table("maps").toArray()); let mapBuffers = {}; for (let map of maps) { @@ -53,8 +60,9 @@ export const versions = { }); }, // v1.3.0 - Added new default tokens - 3(v) { + 3(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(3); return tx .table("states") .toCollection() @@ -116,8 +124,9 @@ export const versions = { }); }, // v1.3.1 - Added show grid option - 4(v) { + 4(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(4); return tx .table("maps") .toCollection() @@ -127,8 +136,9 @@ export const versions = { }); }, // v1.4.0 - Added fog subtraction - 5(v) { + 5(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(5); return tx .table("states") .toCollection() @@ -144,8 +154,9 @@ export const versions = { }); }, // v1.4.2 - Added map resolutions - 6(v) { + 6(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(6); return tx .table("maps") .toCollection() @@ -156,8 +167,9 @@ export const versions = { }); }, // v1.5.0 - Fixed default token rogue spelling - 7(v) { + 7(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(7); return tx .table("states") .toCollection() @@ -171,8 +183,9 @@ export const versions = { }); }, // v1.5.0 - Added map snap to grid option - 8(v) { + 8(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(8); return tx .table("maps") .toCollection() @@ -182,8 +195,9 @@ export const versions = { }); }, // v1.5.1 - Added lock, visibility and modified to tokens - 9(v) { + 9(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(9); return tx .table("states") .toCollection() @@ -199,8 +213,9 @@ export const versions = { }); }, // v1.5.1 - Added token prop category and remove isVehicle bool - 10(v) { + 10(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(10); return tx .table("tokens") .toCollection() @@ -211,8 +226,9 @@ export const versions = { }); }, // v1.5.2 - Added automatic cache invalidation to maps - 11(v) { + 11(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(11); return tx .table("maps") .toCollection() @@ -222,8 +238,9 @@ export const versions = { }); }, // v1.5.2 - Added automatic cache invalidation to tokens - 12(v) { + 12(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(12); return tx .table("tokens") .toCollection() @@ -233,8 +250,9 @@ export const versions = { }); }, // v1.6.0 - Added map grouping and grid scale and offset - 13(v) { + 13(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(13); return tx .table("maps") .toCollection() @@ -256,8 +274,9 @@ export const versions = { }); }, // v1.6.0 - Added token grouping - 14(v) { + 14(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(14); return tx .table("tokens") .toCollection() @@ -267,8 +286,9 @@ export const versions = { }); }, // v1.6.1 - Added width and height to tokens - 15(v) { + 15(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(15); const tokens = await Dexie.waitFor(tx.table("tokens").toArray()); let tokenSizes = {}; for (let token of tokens) { @@ -293,8 +313,9 @@ export const versions = { }); }, // v1.7.0 - Added note tool - 16(v) { + 16(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(16); return tx .table("states") .toCollection() @@ -305,8 +326,9 @@ export const versions = { }); }, // 1.7.0 (hotfix) - Optimized fog shape edits to only include needed data - 17(v) { + 17(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(17); return tx .table("states") .toCollection() @@ -328,8 +350,9 @@ export const versions = { }); }, // 1.8.0 - Added note text only mode, converted draw and fog representations - 18(v) { + 18(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(18); return tx .table("states") .toCollection() @@ -355,8 +378,9 @@ export const versions = { }); }, // 1.8.0 - Add thumbnail to maps and add measurement to grid - 19(v) { + 19(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(19); const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) .value; const maps = await Dexie.waitFor(tx.table("maps").toArray()); @@ -378,8 +402,9 @@ export const versions = { }); }, // 1.8.0 - Add thumbnail to tokens - 20(v) { + 20(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(20); const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) .value; const tokens = await Dexie.waitFor(tx.table("tokens").toArray()); @@ -404,8 +429,9 @@ export const versions = { v.stores({}); }, // v1.8.1 - Shorten fog shape ids - 22(v) { + 22(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(22); return tx .table("states") .toCollection() @@ -420,8 +446,9 @@ export const versions = { }); }, // v1.9.0 - Add outlines to tokens - 23(v) { + 23(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(23); const tokens = await Dexie.waitFor(tx.table("tokens").toArray()); const tokenOutlines = await Dexie.waitFor( Promise.all(tokens.map(createDataOutline)) @@ -447,8 +474,9 @@ export const versions = { }); }, // v1.9.0 - Move map assets into new table - 24(v) { + 24(v, onUpgrade) { v.stores({ assets: "id, owner" }).upgrade((tx) => { + onUpgrade?.(24); tx.table("maps").each((map) => { let assets = []; assets.push({ @@ -493,8 +521,9 @@ export const versions = { }); }, // v1.9.0 - Move token assets into new table - 25(v) { + 25(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(25); tx.table("tokens").each((token) => { let assets = []; assets.push({ @@ -522,8 +551,9 @@ export const versions = { }); }, // v1.9.0 - Create foreign keys for assets - 26(v) { + 26(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(26); tx.table("assets").each((asset) => { if (asset.prevType === "map") { tx.table("maps").update(asset.prevId, { @@ -547,8 +577,9 @@ export const versions = { }); }, // v1.9.0 - Remove asset migration helpers - 27(v) { + 27(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(27); tx.table("assets") .toCollection() .modify((asset) => { @@ -561,8 +592,9 @@ export const versions = { }); }, // v1.9.0 - Remap map resolution assets - 28(v) { + 28(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(28); tx.table("maps") .toCollection() .modify((map) => { @@ -579,8 +611,9 @@ export const versions = { }); }, // v1.9.0 - Move tokens to use more defaults - 29(v) { + 29(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(29); tx.table("tokens") .toCollection() .modify(async (token) => { @@ -592,8 +625,9 @@ export const versions = { }); }, // v1.9.0 - Move tokens to use more defaults and add token outline to token states - 30(v) { + 30(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(30); const tokens = await Dexie.waitFor(tx.table("tokens").toArray()); tx.table("states") .toCollection() @@ -645,8 +679,9 @@ export const versions = { }); }, // v1.9.0 - Remove maps not owned by user as cache is now done on the asset level - 31(v) { + 31(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(31); const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) ?.value; if (userId) { @@ -655,8 +690,9 @@ export const versions = { }); }, // v1.9.0 - Remove tokens not owned by user as cache is now done on the asset level - 32(v) { + 32(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(32); const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) ?.value; if (userId) { @@ -665,8 +701,9 @@ export const versions = { }); }, // v1.9.0 - Store default maps and tokens in db - 33(v) { + 33(v, onUpgrade) { v.stores({}).upgrade(async (tx) => { + onUpgrade?.(33); const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) ?.value; if (!userId) { @@ -679,8 +716,9 @@ export const versions = { }); }, // v1.9.0 - Add new group table - 34(v) { + 34(v, onUpgrade) { v.stores({ groups: "id" }).upgrade(async (tx) => { + onUpgrade?.(34); function groupItems(items) { let groups = []; let subGroups = {}; @@ -714,8 +752,9 @@ export const versions = { }); }, // v1.9.0 - Remove map and token group in respective tables - 35(v) { + 35(v, onUpgrade) { v.stores({}).upgrade((tx) => { + onUpgrade?.(35); tx.table("maps") .toCollection() .modify((map) => { @@ -736,10 +775,11 @@ export const latestVersion = 35; * Load versions onto a database up to a specific version number * @param {Dexie} db * @param {number=} upTo version number to load up to, latest version if undefined + * @param {OnUpgrade=} onUpgrade */ -export function loadVersions(db, upTo = latestVersion) { +export function loadVersions(db, upTo = latestVersion, onUpgrade = undefined) { for (let versionNumber = 1; versionNumber <= upTo; versionNumber++) { - versions[versionNumber](db.version(versionNumber)); + versions[versionNumber](db.version(versionNumber), onUpgrade); } }