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);
}
}