Add on upgrade listener for DB and overlay when upgrading

This commit is contained in:
Mitchell McCaffrey 2021-06-24 16:14:20 +10:00
parent 110a6bdd1f
commit 72f2c74e9d
28 changed files with 302 additions and 143 deletions

View File

@ -12,51 +12,54 @@ import HowTo from "./routes/HowTo";
import Donate from "./routes/Donate"; import Donate from "./routes/Donate";
import { AuthProvider } from "./contexts/AuthContext"; import { AuthProvider } from "./contexts/AuthContext";
import { DatabaseProvider } from "./contexts/DatabaseContext";
import { SettingsProvider } from "./contexts/SettingsContext"; import { SettingsProvider } from "./contexts/SettingsContext";
import { KeyboardProvider } from "./contexts/KeyboardContext"; import { KeyboardProvider } from "./contexts/KeyboardContext";
import { DatabaseProvider } from "./contexts/DatabaseContext";
import { UserIdProvider } from "./contexts/UserIdContext";
import { ToastProvider } from "./components/Toast"; import { ToastProvider } from "./components/Toast";
function App() { function App() {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<DatabaseProvider> <SettingsProvider>
<SettingsProvider> <AuthProvider>
<AuthProvider> <KeyboardProvider>
<KeyboardProvider> <ToastProvider>
<ToastProvider> <Router>
<Router> <Switch>
<Switch> <Route path="/donate">
<Route path="/donate"> <Donate />
<Donate /> </Route>
</Route> {/* Legacy support camel case routes */}
{/* Legacy support camel case routes */} <Route path={["/howTo", "/how-to"]}>
<Route path={["/howTo", "/how-to"]}> <HowTo />
<HowTo /> </Route>
</Route> <Route path={["/releaseNotes", "/release-notes"]}>
<Route path={["/releaseNotes", "/release-notes"]}> <ReleaseNotes />
<ReleaseNotes /> </Route>
</Route> <Route path="/about">
<Route path="/about"> <About />
<About /> </Route>
</Route> <Route path="/faq">
<Route path="/faq"> <FAQ />
<FAQ /> </Route>
</Route> <Route path="/game/:id">
<Route path="/game/:id"> <DatabaseProvider>
<Game /> <UserIdProvider>
</Route> <Game />
<Route path="/"> </UserIdProvider>
<Home /> </DatabaseProvider>
</Route> </Route>
</Switch> <Route path="/">
</Router> <Home />
</ToastProvider> </Route>
</KeyboardProvider> </Switch>
</AuthProvider> </Router>
</SettingsProvider> </ToastProvider>
</DatabaseProvider> </KeyboardProvider>
</AuthProvider>
</SettingsProvider>
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@ -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 bears 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 (
<LoadingOverlay>
<Text as="p" variant="body2" m={1}>
Database upgrading, please wait...
</Text>
{subText && (
<>
<Text
sx={{ maxWidth: "200px", textAlign: "center" }}
as="p"
variant="caption"
m={1}
>
We're still working on the upgrade. In the meantime, did you know?
</Text>
<Text
sx={{ maxWidth: "200px", textAlign: "center" }}
as="p"
variant="body2"
>
{subText}
</Text>
</>
)}
</LoadingOverlay>
);
}
export default UpgradingLoadingOverlay;

View File

@ -14,7 +14,7 @@ import {
} from "../../helpers/token"; } from "../../helpers/token";
import Vector2 from "../../helpers/Vector2"; import Vector2 from "../../helpers/Vector2";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
import { useMapData } from "../../contexts/MapDataContext"; import { useMapData } from "../../contexts/MapDataContext";
import { useTokenData } from "../../contexts/TokenDataContext"; import { useTokenData } from "../../contexts/TokenDataContext";
import { useAssets } from "../../contexts/AssetsContext"; import { useAssets } from "../../contexts/AssetsContext";
@ -25,7 +25,7 @@ import useImageDrop from "../../hooks/useImageDrop";
function GlobalImageDrop({ children, onMapChange, onMapTokensStateCreate }) { function GlobalImageDrop({ children, onMapChange, onMapTokensStateCreate }) {
const { addToast } = useToasts(); const { addToast } = useToasts();
const { userId } = useAuth(); const userId = useUserId();
const { addMap, getMapState } = useMapData(); const { addMap, getMapState } = useMapData();
const { addToken } = useTokenData(); const { addToken } = useTokenData();
const { addAssets } = useAssets(); const { addAssets } = useAssets();

View File

@ -4,7 +4,7 @@ import { Group } from "react-konva";
import { useInteractionEmitter } from "../../contexts/MapInteractionContext"; import { useInteractionEmitter } from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
import Vector2 from "../../helpers/Vector2"; import Vector2 from "../../helpers/Vector2";
import { getRelativePointerPosition } from "../../helpers/konva"; import { getRelativePointerPosition } from "../../helpers/konva";
@ -28,7 +28,7 @@ function MapNotes({
fadeOnHover, fadeOnHover,
}) { }) {
const interactionEmitter = useInteractionEmitter(); const interactionEmitter = useInteractionEmitter();
const { userId } = useAuth(); const userId = useUserId();
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
const [isBrushDown, setIsBrushDown] = useState(false); const [isBrushDown, setIsBrushDown] = useState(false);
const [noteData, setNoteData] = useState(null); const [noteData, setNoteData] = useState(null);

View File

@ -6,7 +6,7 @@ import useImage from "use-image";
import usePrevious from "../../hooks/usePrevious"; import usePrevious from "../../hooks/usePrevious";
import useGridSnapping from "../../hooks/useGridSnapping"; import useGridSnapping from "../../hooks/useGridSnapping";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
import { import {
useSetPreventMapInteraction, useSetPreventMapInteraction,
useMapWidth, useMapWidth,
@ -33,7 +33,7 @@ function MapToken({
fadeOnHover, fadeOnHover,
map, map,
}) { }) {
const { userId } = useAuth(); const userId = useUserId();
const mapWidth = useMapWidth(); const mapWidth = useMapWidth();
const mapHeight = useMapHeight(); const mapHeight = useMapHeight();

View File

@ -5,7 +5,7 @@ import SelectMapModal from "../../modals/SelectMapModal";
import SelectMapIcon from "../../icons/SelectMapIcon"; import SelectMapIcon from "../../icons/SelectMapIcon";
import { useMapData } from "../../contexts/MapDataContext"; import { useMapData } from "../../contexts/MapDataContext";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
function SelectMapButton({ function SelectMapButton({
onMapChange, onMapChange,
@ -17,7 +17,7 @@ function SelectMapButton({
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const { updateMapState } = useMapData(); const { updateMapState } = useMapData();
const { userId } = useAuth(); const userId = useUserId();
function openModal() { function openModal() {
if (currentMapState && currentMap && currentMap.owner === userId) { if (currentMapState && currentMap && currentMap.owner === userId) {
updateMapState(currentMapState.mapId, currentMapState); updateMapState(currentMapState.mapId, currentMapState);

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from "react";
import { Rect, Text } from "react-konva"; import { Rect, Text } from "react-konva";
import { useSpring, animated } from "react-spring/konva"; import { useSpring, animated } from "react-spring/konva";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
import { import {
useSetPreventMapInteraction, useSetPreventMapInteraction,
useMapWidth, useMapWidth,
@ -27,7 +27,7 @@ function Note({
onNoteDragEnd, onNoteDragEnd,
fadeOnHover, fadeOnHover,
}) { }) {
const { userId } = useAuth(); const userId = useUserId();
const mapWidth = useMapWidth(); const mapWidth = useMapWidth();
const mapHeight = useMapHeight(); const mapHeight = useMapHeight();

View File

@ -17,7 +17,7 @@ import HideIcon from "../../icons/TokenHideIcon";
import NoteIcon from "../../icons/NoteToolIcon"; import NoteIcon from "../../icons/NoteToolIcon";
import TextIcon from "../../icons/NoteTextIcon"; import TextIcon from "../../icons/NoteTextIcon";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
const defaultNoteMaxSize = 6; const defaultNoteMaxSize = 6;
@ -29,7 +29,7 @@ function NoteMenu({
onNoteChange, onNoteChange,
map, map,
}) { }) {
const { userId } = useAuth(); const userId = useUserId();
const wasOpen = usePrevious(isOpen); const wasOpen = usePrevious(isOpen);

View File

@ -21,7 +21,7 @@ import useSetting from "../../hooks/useSetting";
import usePreventSelect from "../../hooks/usePreventSelect"; import usePreventSelect from "../../hooks/usePreventSelect";
import { useTokenData } from "../../contexts/TokenDataContext"; import { useTokenData } from "../../contexts/TokenDataContext";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import DragContext from "../../contexts/DragContext"; import DragContext from "../../contexts/DragContext";
@ -33,7 +33,7 @@ import { findGroup } from "../../helpers/group";
import Vector2 from "../../helpers/Vector2"; import Vector2 from "../../helpers/Vector2";
function TokenBar({ onMapTokensStateCreate }) { function TokenBar({ onMapTokensStateCreate }) {
const { userId } = useAuth(); const userId = useUserId();
const { tokensById, tokenGroups } = useTokenData(); const { tokensById, tokenGroups } = useTokenData();
const [fullScreen] = useSetting("map.fullScreen"); const [fullScreen] = useSetting("map.fullScreen");

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
import { import {
useMapWidth, useMapWidth,
useMapHeight, useMapHeight,
@ -16,7 +16,7 @@ function TokenDragOverlay({
tokenGroup, tokenGroup,
dragging, dragging,
}) { }) {
const { userId } = useAuth(); const userId = useUserId();
const mapWidth = useMapWidth(); const mapWidth = useMapWidth();
const mapHeight = useMapHeight(); const mapHeight = useMapHeight();

View File

@ -14,7 +14,7 @@ import UnlockIcon from "../../icons/TokenUnlockIcon";
import ShowIcon from "../../icons/TokenShowIcon"; import ShowIcon from "../../icons/TokenShowIcon";
import HideIcon from "../../icons/TokenHideIcon"; import HideIcon from "../../icons/TokenHideIcon";
import { useAuth } from "../../contexts/AuthContext"; import { useUserId } from "../../contexts/UserIdContext";
const defaultTokenMaxSize = 6; const defaultTokenMaxSize = 6;
function TokenMenu({ function TokenMenu({
@ -25,7 +25,7 @@ function TokenMenu({
onTokenStateChange, onTokenStateChange,
map, map,
}) { }) {
const { userId } = useAuth(); const userId = useUserId();
const wasOpen = usePrevious(isOpen); const wasOpen = usePrevious(isOpen);

View File

@ -50,11 +50,13 @@ const AssetsContext = React.createContext();
const maxCacheSize = 1e8; const maxCacheSize = 1e8;
export function AssetsProvider({ children }) { export function AssetsProvider({ children }) {
const { worker, database } = useDatabase(); const { worker, database, databaseStatus } = useDatabase();
useEffect(() => { useEffect(() => {
worker.cleanAssetCache(maxCacheSize); if (databaseStatus === "loaded") {
}, [worker]); worker.cleanAssetCache(maxCacheSize);
}
}, [worker, databaseStatus]);
const getAsset = useCallback( const getAsset = useCallback(
async (assetId) => { async (assetId) => {

View File

@ -1,7 +1,5 @@
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext } from "react";
import { useDatabase } from "./DatabaseContext";
import FakeStorage from "../helpers/FakeStorage"; import FakeStorage from "../helpers/FakeStorage";
const AuthContext = React.createContext(); const AuthContext = React.createContext();
@ -17,31 +15,13 @@ try {
} }
export function AuthProvider({ children }) { export function AuthProvider({ children }) {
const { database, databaseStatus } = useDatabase();
const [password, setPassword] = useState(storage.getItem("auth") || ""); const [password, setPassword] = useState(storage.getItem("auth") || "");
useEffect(() => { useEffect(() => {
storage.setItem("auth", password); storage.setItem("auth", password);
}, [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 = { const value = {
userId,
password, password,
setPassword, setPassword,
}; };

View File

@ -26,6 +26,7 @@ const worker = Comlink.wrap(new DatabaseWorker());
export function DatabaseProvider({ children }) { export function DatabaseProvider({ children }) {
const [database, setDatabase] = useState(); const [database, setDatabase] = useState();
// "loading" | "disabled" | "upgrading" | "loaded"
const [databaseStatus, setDatabaseStatus] = useState("loading"); const [databaseStatus, setDatabaseStatus] = useState("loading");
const [databaseError, setDatabaseError] = useState(); const [databaseError, setDatabaseError] = useState();
@ -34,7 +35,15 @@ export function DatabaseProvider({ children }) {
let testDBRequest = window.indexedDB.open("__test"); let testDBRequest = window.indexedDB.open("__test");
testDBRequest.onsuccess = async function () { testDBRequest.onsuccess = async function () {
testDBRequest.result.close(); testDBRequest.result.close();
let db = getDatabase({ autoOpen: false }); let db = getDatabase(
{ autoOpen: false },
undefined,
undefined,
true,
(v) => {
setDatabaseStatus("upgrading");
}
);
setDatabase(db); setDatabase(db);
db.on("ready", () => { db.on("ready", () => {
setDatabaseStatus("loaded"); setDatabaseStatus("loaded");

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState, useContext, useCallback } from "react"; import React, { useEffect, useState, useContext, useCallback } from "react";
import { useAuth } from "./AuthContext"; import { useUserId } from "./UserIdContext";
import { useDatabase } from "./DatabaseContext"; import { useDatabase } from "./DatabaseContext";
import { applyObservableChange } from "../helpers/dexie"; import { applyObservableChange } from "../helpers/dexie";
@ -19,7 +19,7 @@ const defaultMapState = {
export function MapDataProvider({ children }) { export function MapDataProvider({ children }) {
const { database, databaseStatus } = useDatabase(); const { database, databaseStatus } = useDatabase();
const { userId } = useAuth(); const userId = useUserId();
const [maps, setMaps] = useState([]); const [maps, setMaps] = useState([]);
const [mapStates, setMapStates] = useState([]); const [mapStates, setMapStates] = useState([]);

View File

@ -1,7 +1,7 @@
import React, { useEffect, useContext } from "react"; import React, { useEffect, useContext } from "react";
import { useDatabase } from "./DatabaseContext"; import { useDatabase } from "./DatabaseContext";
import { useAuth } from "./AuthContext"; import { useUserId } from "./UserIdContext";
import { getRandomMonster } from "../helpers/monsters"; import { getRandomMonster } from "../helpers/monsters";
@ -11,7 +11,7 @@ export const PlayerStateContext = React.createContext();
export const PlayerUpdaterContext = React.createContext(() => {}); export const PlayerUpdaterContext = React.createContext(() => {});
export function PlayerProvider({ session, children }) { export function PlayerProvider({ session, children }) {
const { userId } = useAuth(); const userId = useUserId();
const { database, databaseStatus } = useDatabase(); const { database, databaseStatus } = useDatabase();
const [playerState, setPlayerState] = useNetworkedState( const [playerState, setPlayerState] = useNetworkedState(
@ -53,7 +53,7 @@ export function PlayerProvider({ session, children }) {
if ( if (
playerState.nickname && playerState.nickname &&
database !== undefined && database !== undefined &&
databaseStatus !== "loading" (databaseStatus === "loaded" || databaseStatus === "disabled")
) { ) {
database database
.table("user") .table("user")

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState, useContext, useCallback } from "react"; import React, { useEffect, useState, useContext, useCallback } from "react";
import { useAuth } from "./AuthContext"; import { useUserId } from "./UserIdContext";
import { useDatabase } from "./DatabaseContext"; import { useDatabase } from "./DatabaseContext";
import { applyObservableChange } from "../helpers/dexie"; import { applyObservableChange } from "../helpers/dexie";
@ -10,7 +10,7 @@ const TokenDataContext = React.createContext();
export function TokenDataProvider({ children }) { export function TokenDataProvider({ children }) {
const { database, databaseStatus } = useDatabase(); const { database, databaseStatus } = useDatabase();
const { userId } = useAuth(); const userId = useUserId();
const [tokens, setTokens] = useState([]); const [tokens, setTokens] = useState([]);
const [tokensLoading, setTokensLoading] = useState(true); const [tokensLoading, setTokensLoading] = useState(true);

View File

@ -0,0 +1,36 @@
import React, { useEffect, useState, useContext } from "react";
import { useDatabase } from "./DatabaseContext";
/**
* @type {React.Context<string|undefined>}
*/
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 (
<UserIdContext.Provider value={userId}>{children}</UserIdContext.Provider>
);
}
export function useUserId() {
return useContext(UserIdContext);
}
export default UserIdContext;

View File

@ -3,7 +3,7 @@ import Dexie, { DexieOptions } from "dexie";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import "dexie-observable"; import "dexie-observable";
import { loadVersions, latestVersion } from "./upgrade"; import { loadVersions } from "./upgrade";
import { getDefaultMaps } from "./maps"; import { getDefaultMaps } from "./maps";
import { getDefaultTokens } from "./tokens"; import { getDefaultTokens } from "./tokens";
@ -36,16 +36,18 @@ function populate(db) {
* @param {string=} name * @param {string=} name
* @param {number=} versionNumber * @param {number=} versionNumber
* @param {boolean=} populateData * @param {boolean=} populateData
* @param {import("./upgrade").OnUpgrade=} onUpgrade
* @returns {Dexie} * @returns {Dexie}
*/ */
export function getDatabase( export function getDatabase(
options, options,
name = "OwlbearRodeoDB", name = "OwlbearRodeoDB",
versionNumber = latestVersion, versionNumber = undefined,
populateData = true populateData = true,
onUpgrade = undefined
) { ) {
let db = new Dexie(name, options); let db = new Dexie(name, options);
loadVersions(db, versionNumber); loadVersions(db, versionNumber, onUpgrade);
if (populateData) { if (populateData) {
populate(db); populate(db);
} }

View File

@ -19,7 +19,7 @@ import {
useDebouncedStageScale, useDebouncedStageScale,
} from "../contexts/MapInteractionContext"; } from "../contexts/MapInteractionContext";
import { MapStageProvider, useMapStage } from "../contexts/MapStageContext"; 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 SettingsContext, { useSettings } from "../contexts/SettingsContext";
import KeyboardContext from "../contexts/KeyboardContext"; import KeyboardContext from "../contexts/KeyboardContext";
import AssetsContext, { import AssetsContext, {
@ -50,7 +50,7 @@ import DatabaseContext, { useDatabase } from "../contexts/DatabaseContext";
*/ */
function KonvaBridge({ stageRender, children }) { function KonvaBridge({ stageRender, children }) {
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
const auth = useAuth(); const userId = useUserId();
const settings = useSettings(); const settings = useSettings();
const assets = useAssets(); const assets = useAssets();
const assetURLs = useContext(AssetURLsStateContext); const assetURLs = useContext(AssetURLsStateContext);
@ -78,7 +78,7 @@ function KonvaBridge({ stageRender, children }) {
return stageRender( return stageRender(
<DatabaseContext.Provider value={database}> <DatabaseContext.Provider value={database}>
<AuthContext.Provider value={auth}> <UserIdContext.Provider value={userId}>
<SettingsContext.Provider value={settings}> <SettingsContext.Provider value={settings}>
<KeyboardContext.Provider value={keyboardValue}> <KeyboardContext.Provider value={keyboardValue}>
<MapStageProvider value={mapStageRef}> <MapStageProvider value={mapStageRef}>
@ -140,7 +140,7 @@ function KonvaBridge({ stageRender, children }) {
</MapStageProvider> </MapStageProvider>
</KeyboardContext.Provider> </KeyboardContext.Provider>
</SettingsContext.Provider> </SettingsContext.Provider>
</AuthContext.Provider> </UserIdContext.Provider>
</DatabaseContext.Provider> </DatabaseContext.Provider>
); );
} }

View File

@ -11,7 +11,7 @@ import LoadingOverlay from "../components/LoadingOverlay";
import LoadingBar from "../components/LoadingBar"; import LoadingBar from "../components/LoadingBar";
import ErrorBanner from "../components/banner/ErrorBanner"; import ErrorBanner from "../components/banner/ErrorBanner";
import { useAuth } from "../contexts/AuthContext"; import { useUserId } from "../contexts/UserIdContext";
import { useDatabase } from "../contexts/DatabaseContext"; import { useDatabase } from "../contexts/DatabaseContext";
import SelectDataModal from "./SelectDataModal"; import SelectDataModal from "./SelectDataModal";
@ -22,7 +22,7 @@ const importDBName = "OwlbearRodeoImportDB";
function ImportExportModal({ isOpen, onRequestClose }) { function ImportExportModal({ isOpen, onRequestClose }) {
const { worker } = useDatabase(); const { worker } = useDatabase();
const { userId } = useAuth(); const userId = useUserId();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(); const [error, setError] = useState();

View File

@ -25,7 +25,7 @@ import { createMapFromFile } from "../helpers/map";
import useResponsiveLayout from "../hooks/useResponsiveLayout"; import useResponsiveLayout from "../hooks/useResponsiveLayout";
import { useMapData } from "../contexts/MapDataContext"; import { useMapData } from "../contexts/MapDataContext";
import { useAuth } from "../contexts/AuthContext"; import { useUserId } from "../contexts/UserIdContext";
import { useAssets } from "../contexts/AssetsContext"; import { useAssets } from "../contexts/AssetsContext";
import { GroupProvider } from "../contexts/GroupContext"; import { GroupProvider } from "../contexts/GroupContext";
import { TileDragProvider } from "../contexts/TileDragContext"; import { TileDragProvider } from "../contexts/TileDragContext";
@ -40,7 +40,7 @@ function SelectMapModal({
}) { }) {
const { addToast } = useToasts(); const { addToast } = useToasts();
const { userId } = useAuth(); const userId = useUserId();
const { const {
maps, maps,
mapStates, mapStates,

View File

@ -29,7 +29,7 @@ import Vector2 from "../helpers/Vector2";
import useResponsiveLayout from "../hooks/useResponsiveLayout"; import useResponsiveLayout from "../hooks/useResponsiveLayout";
import { useTokenData } from "../contexts/TokenDataContext"; import { useTokenData } from "../contexts/TokenDataContext";
import { useAuth } from "../contexts/AuthContext"; import { useUserId } from "../contexts/UserIdContext";
import { useAssets } from "../contexts/AssetsContext"; import { useAssets } from "../contexts/AssetsContext";
import { GroupProvider } from "../contexts/GroupContext"; import { GroupProvider } from "../contexts/GroupContext";
import { TileDragProvider } from "../contexts/TileDragContext"; import { TileDragProvider } from "../contexts/TileDragContext";
@ -38,7 +38,7 @@ import { useMapStage } from "../contexts/MapStageContext";
function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) { function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) {
const { addToast } = useToasts(); const { addToast } = useToasts();
const { userId } = useAuth(); const userId = useUserId();
const { const {
tokens, tokens,
addToken, addToken,

View File

@ -14,7 +14,7 @@ import Modal from "../components/Modal";
import Slider from "../components/Slider"; import Slider from "../components/Slider";
import LoadingOverlay from "../components/LoadingOverlay"; import LoadingOverlay from "../components/LoadingOverlay";
import { useAuth } from "../contexts/AuthContext"; import { useUserId } from "../contexts/UserIdContext";
import { useDatabase } from "../contexts/DatabaseContext"; import { useDatabase } from "../contexts/DatabaseContext";
import useSetting from "../hooks/useSetting"; import useSetting from "../hooks/useSetting";
@ -24,7 +24,7 @@ import ImportExportModal from "./ImportExportModal";
function SettingsModal({ isOpen, onRequestClose }) { function SettingsModal({ isOpen, onRequestClose }) {
const { database, databaseStatus } = useDatabase(); const { database, databaseStatus } = useDatabase();
const { userId } = useAuth(); const userId = useUserId();
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [labelSize, setLabelSize] = useSetting("map.labelSize"); const [labelSize, setLabelSize] = useSetting("map.labelSize");
const [gridSnappingSensitivity, setGridSnappingSensitivity] = useSetting( const [gridSnappingSensitivity, setGridSnappingSensitivity] = useSetting(

View File

@ -3,7 +3,7 @@ import { useToasts } from "react-toast-notifications";
import { useMapData } from "../contexts/MapDataContext"; import { useMapData } from "../contexts/MapDataContext";
import { useMapLoading } from "../contexts/MapLoadingContext"; import { useMapLoading } from "../contexts/MapLoadingContext";
import { useAuth } from "../contexts/AuthContext"; import { useUserId } from "../contexts/UserIdContext";
import { useDatabase } from "../contexts/DatabaseContext"; import { useDatabase } from "../contexts/DatabaseContext";
import { useParty } from "../contexts/PartyContext"; import { useParty } from "../contexts/PartyContext";
import { useAssets } from "../contexts/AssetsContext"; import { useAssets } from "../contexts/AssetsContext";
@ -39,7 +39,7 @@ const defaultMapActions = {
*/ */
function NetworkedMapAndTokens({ session }) { function NetworkedMapAndTokens({ session }) {
const { addToast } = useToasts(); const { addToast } = useToasts();
const { userId } = useAuth(); const userId = useUserId();
const partyState = useParty(); const partyState = useParty();
const { assetLoadStart, assetProgressUpdate, isLoading } = useMapLoading(); const { assetLoadStart, assetProgressUpdate, isLoading } = useMapLoading();

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { Group } from "react-konva"; import { Group } from "react-konva";
import { useAuth } from "../contexts/AuthContext"; import { useUserId } from "../contexts/UserIdContext";
import MapPointer from "../components/map/MapPointer"; import MapPointer from "../components/map/MapPointer";
import { isEmpty } from "../helpers/shared"; import { isEmpty } from "../helpers/shared";
@ -13,7 +13,7 @@ import useSetting from "../hooks/useSetting";
const sendTickRate = 50; const sendTickRate = 50;
function NetworkedMapPointer({ session, active }) { function NetworkedMapPointer({ session, active }) {
const { userId } = useAuth(); const userId = useUserId();
const [localPointerState, setLocalPointerState] = useState({}); const [localPointerState, setLocalPointerState] = useState({});
const [pointerColor] = useSetting("pointer.color"); const [pointerColor] = useSetting("pointer.color");

View File

@ -8,6 +8,7 @@ import OfflineBanner from "../components/banner/OfflineBanner";
import LoadingOverlay from "../components/LoadingOverlay"; import LoadingOverlay from "../components/LoadingOverlay";
import Link from "../components/Link"; import Link from "../components/Link";
import MapLoadingOverlay from "../components/map/MapLoadingOverlay"; import MapLoadingOverlay from "../components/map/MapLoadingOverlay";
import UpgradingLoadingOverlay from "../components/UpgradingLoadingOverlay";
import AuthModal from "../modals/AuthModal"; import AuthModal from "../modals/AuthModal";
import GameExpiredModal from "../modals/GameExpiredModal"; import GameExpiredModal from "../modals/GameExpiredModal";
@ -90,13 +91,16 @@ function Game() {
// Join game // Join game
useEffect(() => { useEffect(() => {
if (sessionStatus === "ready" && databaseStatus !== "loading") { if (
sessionStatus === "ready" &&
(databaseStatus === "loaded" || databaseStatus === "disabled")
) {
session.joinGame(gameId, password); session.joinGame(gameId, password);
} }
}, [gameId, password, databaseStatus, session, sessionStatus]); }, [gameId, password, databaseStatus, session, sessionStatus]);
function handleAuthSubmit(newPassword) { function handleAuthSubmit(newPassword) {
if (databaseStatus !== "loading") { if (databaseStatus === "loaded" || databaseStatus === "disabled") {
session.joinGame(gameId, newPassword); session.joinGame(gameId, newPassword);
} }
} }
@ -151,6 +155,9 @@ function Game() {
isOpen={sessionStatus === "needs_update"} isOpen={sessionStatus === "needs_update"}
/> />
{!sessionStatus && <LoadingOverlay />} {!sessionStatus && <LoadingOverlay />}
{sessionStatus && databaseStatus === "upgrading" && (
<UpgradingLoadingOverlay />
)}
<MapLoadingOverlay /> <MapLoadingOverlay />
</MapStageProvider> </MapStageProvider>
</PartyProvider> </PartyProvider>

View File

@ -17,9 +17,15 @@ import {
import { getDefaultMaps } from "./maps"; import { getDefaultMaps } from "./maps";
import { getDefaultTokens } from "./tokens"; import { getDefaultTokens } from "./tokens";
/**
* @callback OnUpgrade
* @param {number} versionNumber
*/
/** /**
* @callback VersionCallback * @callback VersionCallback
* @param {Version} version * @param {Version} version
* @param {OnUpgrade=} onUpgrade
*/ */
/** /**
@ -37,8 +43,9 @@ export const versions = {
}); });
}, },
// v1.2.1 - Move from blob files to array buffers // v1.2.1 - Move from blob files to array buffers
2(v) { 2(v, onUpgrade) {
v.stores({}).upgrade(async (tx) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(2);
const maps = await Dexie.waitFor(tx.table("maps").toArray()); const maps = await Dexie.waitFor(tx.table("maps").toArray());
let mapBuffers = {}; let mapBuffers = {};
for (let map of maps) { for (let map of maps) {
@ -53,8 +60,9 @@ export const versions = {
}); });
}, },
// v1.3.0 - Added new default tokens // v1.3.0 - Added new default tokens
3(v) { 3(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(3);
return tx return tx
.table("states") .table("states")
.toCollection() .toCollection()
@ -116,8 +124,9 @@ export const versions = {
}); });
}, },
// v1.3.1 - Added show grid option // v1.3.1 - Added show grid option
4(v) { 4(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(4);
return tx return tx
.table("maps") .table("maps")
.toCollection() .toCollection()
@ -127,8 +136,9 @@ export const versions = {
}); });
}, },
// v1.4.0 - Added fog subtraction // v1.4.0 - Added fog subtraction
5(v) { 5(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(5);
return tx return tx
.table("states") .table("states")
.toCollection() .toCollection()
@ -144,8 +154,9 @@ export const versions = {
}); });
}, },
// v1.4.2 - Added map resolutions // v1.4.2 - Added map resolutions
6(v) { 6(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(6);
return tx return tx
.table("maps") .table("maps")
.toCollection() .toCollection()
@ -156,8 +167,9 @@ export const versions = {
}); });
}, },
// v1.5.0 - Fixed default token rogue spelling // v1.5.0 - Fixed default token rogue spelling
7(v) { 7(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(7);
return tx return tx
.table("states") .table("states")
.toCollection() .toCollection()
@ -171,8 +183,9 @@ export const versions = {
}); });
}, },
// v1.5.0 - Added map snap to grid option // v1.5.0 - Added map snap to grid option
8(v) { 8(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(8);
return tx return tx
.table("maps") .table("maps")
.toCollection() .toCollection()
@ -182,8 +195,9 @@ export const versions = {
}); });
}, },
// v1.5.1 - Added lock, visibility and modified to tokens // v1.5.1 - Added lock, visibility and modified to tokens
9(v) { 9(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(9);
return tx return tx
.table("states") .table("states")
.toCollection() .toCollection()
@ -199,8 +213,9 @@ export const versions = {
}); });
}, },
// v1.5.1 - Added token prop category and remove isVehicle bool // v1.5.1 - Added token prop category and remove isVehicle bool
10(v) { 10(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(10);
return tx return tx
.table("tokens") .table("tokens")
.toCollection() .toCollection()
@ -211,8 +226,9 @@ export const versions = {
}); });
}, },
// v1.5.2 - Added automatic cache invalidation to maps // v1.5.2 - Added automatic cache invalidation to maps
11(v) { 11(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(11);
return tx return tx
.table("maps") .table("maps")
.toCollection() .toCollection()
@ -222,8 +238,9 @@ export const versions = {
}); });
}, },
// v1.5.2 - Added automatic cache invalidation to tokens // v1.5.2 - Added automatic cache invalidation to tokens
12(v) { 12(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(12);
return tx return tx
.table("tokens") .table("tokens")
.toCollection() .toCollection()
@ -233,8 +250,9 @@ export const versions = {
}); });
}, },
// v1.6.0 - Added map grouping and grid scale and offset // v1.6.0 - Added map grouping and grid scale and offset
13(v) { 13(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(13);
return tx return tx
.table("maps") .table("maps")
.toCollection() .toCollection()
@ -256,8 +274,9 @@ export const versions = {
}); });
}, },
// v1.6.0 - Added token grouping // v1.6.0 - Added token grouping
14(v) { 14(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(14);
return tx return tx
.table("tokens") .table("tokens")
.toCollection() .toCollection()
@ -267,8 +286,9 @@ export const versions = {
}); });
}, },
// v1.6.1 - Added width and height to tokens // v1.6.1 - Added width and height to tokens
15(v) { 15(v, onUpgrade) {
v.stores({}).upgrade(async (tx) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(15);
const tokens = await Dexie.waitFor(tx.table("tokens").toArray()); const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
let tokenSizes = {}; let tokenSizes = {};
for (let token of tokens) { for (let token of tokens) {
@ -293,8 +313,9 @@ export const versions = {
}); });
}, },
// v1.7.0 - Added note tool // v1.7.0 - Added note tool
16(v) { 16(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(16);
return tx return tx
.table("states") .table("states")
.toCollection() .toCollection()
@ -305,8 +326,9 @@ export const versions = {
}); });
}, },
// 1.7.0 (hotfix) - Optimized fog shape edits to only include needed data // 1.7.0 (hotfix) - Optimized fog shape edits to only include needed data
17(v) { 17(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(17);
return tx return tx
.table("states") .table("states")
.toCollection() .toCollection()
@ -328,8 +350,9 @@ export const versions = {
}); });
}, },
// 1.8.0 - Added note text only mode, converted draw and fog representations // 1.8.0 - Added note text only mode, converted draw and fog representations
18(v) { 18(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(18);
return tx return tx
.table("states") .table("states")
.toCollection() .toCollection()
@ -355,8 +378,9 @@ export const versions = {
}); });
}, },
// 1.8.0 - Add thumbnail to maps and add measurement to grid // 1.8.0 - Add thumbnail to maps and add measurement to grid
19(v) { 19(v, onUpgrade) {
v.stores({}).upgrade(async (tx) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(19);
const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
.value; .value;
const maps = await Dexie.waitFor(tx.table("maps").toArray()); const maps = await Dexie.waitFor(tx.table("maps").toArray());
@ -378,8 +402,9 @@ export const versions = {
}); });
}, },
// 1.8.0 - Add thumbnail to tokens // 1.8.0 - Add thumbnail to tokens
20(v) { 20(v, onUpgrade) {
v.stores({}).upgrade(async (tx) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(20);
const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
.value; .value;
const tokens = await Dexie.waitFor(tx.table("tokens").toArray()); const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
@ -404,8 +429,9 @@ export const versions = {
v.stores({}); v.stores({});
}, },
// v1.8.1 - Shorten fog shape ids // v1.8.1 - Shorten fog shape ids
22(v) { 22(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(22);
return tx return tx
.table("states") .table("states")
.toCollection() .toCollection()
@ -420,8 +446,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Add outlines to tokens // v1.9.0 - Add outlines to tokens
23(v) { 23(v, onUpgrade) {
v.stores({}).upgrade(async (tx) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(23);
const tokens = await Dexie.waitFor(tx.table("tokens").toArray()); const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
const tokenOutlines = await Dexie.waitFor( const tokenOutlines = await Dexie.waitFor(
Promise.all(tokens.map(createDataOutline)) Promise.all(tokens.map(createDataOutline))
@ -447,8 +474,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Move map assets into new table // v1.9.0 - Move map assets into new table
24(v) { 24(v, onUpgrade) {
v.stores({ assets: "id, owner" }).upgrade((tx) => { v.stores({ assets: "id, owner" }).upgrade((tx) => {
onUpgrade?.(24);
tx.table("maps").each((map) => { tx.table("maps").each((map) => {
let assets = []; let assets = [];
assets.push({ assets.push({
@ -493,8 +521,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Move token assets into new table // v1.9.0 - Move token assets into new table
25(v) { 25(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(25);
tx.table("tokens").each((token) => { tx.table("tokens").each((token) => {
let assets = []; let assets = [];
assets.push({ assets.push({
@ -522,8 +551,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Create foreign keys for assets // v1.9.0 - Create foreign keys for assets
26(v) { 26(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(26);
tx.table("assets").each((asset) => { tx.table("assets").each((asset) => {
if (asset.prevType === "map") { if (asset.prevType === "map") {
tx.table("maps").update(asset.prevId, { tx.table("maps").update(asset.prevId, {
@ -547,8 +577,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Remove asset migration helpers // v1.9.0 - Remove asset migration helpers
27(v) { 27(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(27);
tx.table("assets") tx.table("assets")
.toCollection() .toCollection()
.modify((asset) => { .modify((asset) => {
@ -561,8 +592,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Remap map resolution assets // v1.9.0 - Remap map resolution assets
28(v) { 28(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(28);
tx.table("maps") tx.table("maps")
.toCollection() .toCollection()
.modify((map) => { .modify((map) => {
@ -579,8 +611,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Move tokens to use more defaults // v1.9.0 - Move tokens to use more defaults
29(v) { 29(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(29);
tx.table("tokens") tx.table("tokens")
.toCollection() .toCollection()
.modify(async (token) => { .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 // 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) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(30);
const tokens = await Dexie.waitFor(tx.table("tokens").toArray()); const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
tx.table("states") tx.table("states")
.toCollection() .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 // 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) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(31);
const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
?.value; ?.value;
if (userId) { 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 // 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) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(32);
const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
?.value; ?.value;
if (userId) { if (userId) {
@ -665,8 +701,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Store default maps and tokens in db // v1.9.0 - Store default maps and tokens in db
33(v) { 33(v, onUpgrade) {
v.stores({}).upgrade(async (tx) => { v.stores({}).upgrade(async (tx) => {
onUpgrade?.(33);
const userId = (await Dexie.waitFor(tx.table("user").get("userId"))) const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
?.value; ?.value;
if (!userId) { if (!userId) {
@ -679,8 +716,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Add new group table // v1.9.0 - Add new group table
34(v) { 34(v, onUpgrade) {
v.stores({ groups: "id" }).upgrade(async (tx) => { v.stores({ groups: "id" }).upgrade(async (tx) => {
onUpgrade?.(34);
function groupItems(items) { function groupItems(items) {
let groups = []; let groups = [];
let subGroups = {}; let subGroups = {};
@ -714,8 +752,9 @@ export const versions = {
}); });
}, },
// v1.9.0 - Remove map and token group in respective tables // v1.9.0 - Remove map and token group in respective tables
35(v) { 35(v, onUpgrade) {
v.stores({}).upgrade((tx) => { v.stores({}).upgrade((tx) => {
onUpgrade?.(35);
tx.table("maps") tx.table("maps")
.toCollection() .toCollection()
.modify((map) => { .modify((map) => {
@ -736,10 +775,11 @@ export const latestVersion = 35;
* Load versions onto a database up to a specific version number * Load versions onto a database up to a specific version number
* @param {Dexie} db * @param {Dexie} db
* @param {number=} upTo version number to load up to, latest version if undefined * @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++) { for (let versionNumber = 1; versionNumber <= upTo; versionNumber++) {
versions[versionNumber](db.version(versionNumber)); versions[versionNumber](db.version(versionNumber), onUpgrade);
} }
} }