commit
9e8898f769
@ -21,8 +21,8 @@
|
||||
"color": "^3.1.3",
|
||||
"comlink": "^4.3.0",
|
||||
"deep-diff": "^1.0.2",
|
||||
"dexie": "^3.0.3",
|
||||
"dexie-observable": "^3.0.0-beta.10",
|
||||
"dexie": "3.1.0-beta.13",
|
||||
"dexie-react-hooks": "^1.0.6",
|
||||
"err-code": "^3.0.1",
|
||||
"fake-indexeddb": "^3.1.2",
|
||||
"file-saver": "^2.0.5",
|
||||
@ -30,6 +30,7 @@
|
||||
"image-outline": "^0.1.0",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"konva": "^7.2.5",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState, useContext, useCallback, useEffect } from "react";
|
||||
import * as Comlink from "comlink";
|
||||
import { encode } from "@msgpack/msgpack";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
|
||||
import { useDatabase } from "./DatabaseContext";
|
||||
|
||||
@ -128,6 +129,47 @@ export const AssetURLsUpdaterContext = React.createContext();
|
||||
*/
|
||||
export function AssetURLsProvider({ children }) {
|
||||
const [assetURLs, setAssetURLs] = useState({});
|
||||
const { database } = useDatabase();
|
||||
|
||||
// Keep track of when the asset keys change so we can update the URLs
|
||||
const [assetKeys, setAssetKeys] = useState([]);
|
||||
useEffect(() => {
|
||||
const keys = Object.keys(assetURLs);
|
||||
let newKeys = keys.filter((key) => !assetKeys.includes(key));
|
||||
let deletedKeys = assetKeys.filter((key) => !keys.includes(key));
|
||||
if (newKeys.length > 0 || deletedKeys.length > 0) {
|
||||
setAssetKeys((prevKeys) =>
|
||||
[...prevKeys, ...newKeys].filter((key) => !deletedKeys.includes(key))
|
||||
);
|
||||
}
|
||||
}, [assetURLs, assetKeys]);
|
||||
|
||||
// Get the new assets whenever the keys change
|
||||
const assets = useLiveQuery(
|
||||
() => database?.table("assets").where("id").anyOf(assetKeys).toArray(),
|
||||
[database, assetKeys]
|
||||
);
|
||||
|
||||
// Update asset URLs when assets are loaded
|
||||
useEffect(() => {
|
||||
if (!assets) {
|
||||
return;
|
||||
}
|
||||
setAssetURLs((prevURLs) => {
|
||||
let newURLs = { ...prevURLs };
|
||||
for (let asset of assets) {
|
||||
if (newURLs[asset.id].url === null) {
|
||||
newURLs[asset.id] = {
|
||||
...newURLs[asset.id],
|
||||
url: URL.createObjectURL(
|
||||
new Blob([asset.file], { type: asset.mime })
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
return newURLs;
|
||||
});
|
||||
}, [assets]);
|
||||
|
||||
// Clean up asset URLs every minute
|
||||
const debouncedAssetURLs = useDebounce(assetURLs, 60 * 1000);
|
||||
@ -177,16 +219,8 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) {
|
||||
throw new Error("useAssetURL must be used within a AssetURLsProvider");
|
||||
}
|
||||
|
||||
const { getAsset } = useAssets();
|
||||
const { database, databaseStatus } = useDatabase();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!assetId ||
|
||||
type !== "file" ||
|
||||
!database ||
|
||||
databaseStatus === "loading"
|
||||
) {
|
||||
if (!assetId || type !== "file") {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -201,13 +235,10 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) {
|
||||
};
|
||||
}
|
||||
|
||||
function createURL(prevURLs, asset) {
|
||||
const url = URL.createObjectURL(
|
||||
new Blob([asset.file], { type: asset.mime })
|
||||
);
|
||||
function createReference(prevURLs) {
|
||||
return {
|
||||
...prevURLs,
|
||||
[assetId]: { url, id: assetId, references: 1 },
|
||||
[assetId]: { url: null, id: assetId, references: 1 },
|
||||
};
|
||||
}
|
||||
setAssetURLs((prevURLs) => {
|
||||
@ -215,59 +246,14 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) {
|
||||
// Check if the asset url is already added and increase references
|
||||
return increaseReferences(prevURLs);
|
||||
} else {
|
||||
getAsset(assetId).then((asset) => {
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
setAssetURLs((prevURLs) => {
|
||||
if (assetId in prevURLs) {
|
||||
// Check again if it exists
|
||||
return increaseReferences(prevURLs);
|
||||
} else {
|
||||
// Create url if the asset doesn't have a url
|
||||
return createURL(prevURLs, asset);
|
||||
}
|
||||
});
|
||||
});
|
||||
return prevURLs;
|
||||
return createReference(prevURLs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateAssetURL();
|
||||
|
||||
// Update the url when the asset is added to the db after the hook is used
|
||||
function handleAssetChanges(changes) {
|
||||
for (let change of changes) {
|
||||
const id = change.key;
|
||||
if (
|
||||
change.table === "assets" &&
|
||||
id === assetId &&
|
||||
(change.type === 1 || change.type === 2)
|
||||
) {
|
||||
const asset = change.obj;
|
||||
setAssetURLs((prevURLs) => {
|
||||
if (!(assetId in prevURLs)) {
|
||||
const url = URL.createObjectURL(
|
||||
new Blob([asset.file], { type: asset.mime })
|
||||
);
|
||||
return {
|
||||
...prevURLs,
|
||||
[assetId]: { url, id: assetId, references: 1 },
|
||||
};
|
||||
} else {
|
||||
return prevURLs;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
database.on("changes", handleAssetChanges);
|
||||
|
||||
return () => {
|
||||
database.on("changes").unsubscribe(handleAssetChanges);
|
||||
|
||||
// Decrease references
|
||||
setAssetURLs((prevURLs) => {
|
||||
if (assetId in prevURLs) {
|
||||
@ -283,7 +269,7 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) {
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [assetId, setAssetURLs, getAsset, type, database, databaseStatus]);
|
||||
}, [assetId, setAssetURLs, type]);
|
||||
|
||||
if (!assetId) {
|
||||
return unknownSource;
|
||||
|
@ -40,7 +40,7 @@ export function DatabaseProvider({ children }) {
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
(v) => {
|
||||
() => {
|
||||
setDatabaseStatus("upgrading");
|
||||
}
|
||||
);
|
||||
|
@ -1,9 +1,14 @@
|
||||
import React, { useEffect, useState, useContext, useCallback } from "react";
|
||||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
useContext,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
|
||||
import { useUserId } from "./UserIdContext";
|
||||
import { useDatabase } from "./DatabaseContext";
|
||||
|
||||
import { applyObservableChange } from "../helpers/dexie";
|
||||
import { removeGroupsItems } from "../helpers/group";
|
||||
|
||||
const MapDataContext = React.createContext();
|
||||
@ -18,33 +23,39 @@ const defaultMapState = {
|
||||
};
|
||||
|
||||
export function MapDataProvider({ children }) {
|
||||
const { database, databaseStatus } = useDatabase();
|
||||
const userId = useUserId();
|
||||
const { database } = useDatabase();
|
||||
|
||||
const mapsQuery = useLiveQuery(
|
||||
() => database?.table("maps").toArray(),
|
||||
[database]
|
||||
);
|
||||
const mapStatesQuery = useLiveQuery(
|
||||
() => database?.table("states").toArray(),
|
||||
[database]
|
||||
);
|
||||
|
||||
const maps = useMemo(() => mapsQuery || [], [mapsQuery]);
|
||||
const mapStates = useMemo(() => mapStatesQuery || [], [mapStatesQuery]);
|
||||
const mapsLoading = useMemo(
|
||||
() => !mapsQuery || !mapStatesQuery,
|
||||
[mapsQuery, mapStatesQuery]
|
||||
);
|
||||
|
||||
const mapGroupQuery = useLiveQuery(
|
||||
() => database?.table("groups").get("maps"),
|
||||
[database]
|
||||
);
|
||||
|
||||
const [maps, setMaps] = useState([]);
|
||||
const [mapStates, setMapStates] = useState([]);
|
||||
const [mapsLoading, setMapsLoading] = useState(true);
|
||||
const [mapGroups, setMapGroups] = useState([]);
|
||||
|
||||
// Load maps from the database and ensure state is properly setup
|
||||
useEffect(() => {
|
||||
if (!userId || !database || databaseStatus === "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
async function loadMaps() {
|
||||
const storedMaps = await database.table("maps").toArray();
|
||||
setMaps(storedMaps);
|
||||
const storedStates = await database.table("states").toArray();
|
||||
setMapStates(storedStates);
|
||||
async function updateMapGroups() {
|
||||
const group = await database.table("groups").get("maps");
|
||||
const storedGroups = group.items;
|
||||
setMapGroups(storedGroups);
|
||||
setMapsLoading(false);
|
||||
setMapGroups(group.items);
|
||||
}
|
||||
|
||||
loadMaps();
|
||||
}, [userId, database, databaseStatus]);
|
||||
if (database && mapGroupQuery) {
|
||||
updateMapGroups();
|
||||
}
|
||||
}, [mapGroupQuery, database]);
|
||||
|
||||
const getMap = useCallback(
|
||||
async (mapId) => {
|
||||
@ -138,77 +149,6 @@ export function MapDataProvider({ children }) {
|
||||
[database]
|
||||
);
|
||||
|
||||
// Create DB observable to sync creating and deleting
|
||||
useEffect(() => {
|
||||
if (!database || databaseStatus === "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleMapChanges(changes) {
|
||||
for (let change of changes) {
|
||||
if (change.table === "maps") {
|
||||
if (change.type === 1) {
|
||||
// Created
|
||||
const map = change.obj;
|
||||
const state = { ...defaultMapState, mapId: map.id };
|
||||
setMaps((prevMaps) => [map, ...prevMaps]);
|
||||
setMapStates((prevStates) => [state, ...prevStates]);
|
||||
} else if (change.type === 2) {
|
||||
const map = change.obj;
|
||||
setMaps((prevMaps) => {
|
||||
const newMaps = [...prevMaps];
|
||||
const i = newMaps.findIndex((m) => m.id === map.id);
|
||||
if (i > -1) {
|
||||
newMaps[i] = map;
|
||||
}
|
||||
return newMaps;
|
||||
});
|
||||
} else if (change.type === 3) {
|
||||
// Deleted
|
||||
const id = change.key;
|
||||
setMaps((prevMaps) => {
|
||||
const filtered = prevMaps.filter((map) => map.id !== id);
|
||||
return filtered;
|
||||
});
|
||||
setMapStates((prevMapsStates) => {
|
||||
const filtered = prevMapsStates.filter(
|
||||
(state) => state.mapId !== id
|
||||
);
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (change.table === "states") {
|
||||
if (change.type === 2) {
|
||||
// Update map state
|
||||
const state = change.obj;
|
||||
setMapStates((prevMapStates) => {
|
||||
const newStates = [...prevMapStates];
|
||||
const i = newStates.findIndex((s) => s.mapId === state.mapId);
|
||||
if (i > -1) {
|
||||
newStates[i] = state;
|
||||
}
|
||||
return newStates;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (change.table === "groups") {
|
||||
if (change.type === 2 && change.key === "maps") {
|
||||
const group = applyObservableChange(change);
|
||||
const groups = group.items.filter((item) => item !== null);
|
||||
setMapGroups(groups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
database.on("changes", handleMapChanges);
|
||||
|
||||
return () => {
|
||||
database.on("changes").unsubscribe(handleMapChanges);
|
||||
};
|
||||
}, [database, databaseStatus]);
|
||||
|
||||
const [mapsById, setMapsById] = useState({});
|
||||
useEffect(() => {
|
||||
setMapsById(
|
||||
|
@ -1,37 +1,44 @@
|
||||
import React, { useEffect, useState, useContext, useCallback } from "react";
|
||||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
useContext,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
|
||||
import { useUserId } from "./UserIdContext";
|
||||
import { useDatabase } from "./DatabaseContext";
|
||||
|
||||
import { applyObservableChange } from "../helpers/dexie";
|
||||
import { removeGroupsItems } from "../helpers/group";
|
||||
|
||||
const TokenDataContext = React.createContext();
|
||||
|
||||
export function TokenDataProvider({ children }) {
|
||||
const { database, databaseStatus } = useDatabase();
|
||||
const userId = useUserId();
|
||||
const { database } = useDatabase();
|
||||
|
||||
const tokensQuery = useLiveQuery(
|
||||
() => database?.table("tokens").toArray(),
|
||||
[database]
|
||||
);
|
||||
|
||||
const tokens = useMemo(() => tokensQuery || [], [tokensQuery]);
|
||||
const tokensLoading = useMemo(() => !tokensQuery, [tokensQuery]);
|
||||
|
||||
const tokenGroupQuery = useLiveQuery(
|
||||
() => database?.table("groups").get("tokens"),
|
||||
[database]
|
||||
);
|
||||
|
||||
const [tokens, setTokens] = useState([]);
|
||||
const [tokensLoading, setTokensLoading] = useState(true);
|
||||
const [tokenGroups, setTokenGroups] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId || !database || databaseStatus === "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
async function loadTokens() {
|
||||
const storedTokens = await database.table("tokens").toArray();
|
||||
setTokens(storedTokens);
|
||||
async function updateTokenGroups() {
|
||||
const group = await database.table("groups").get("tokens");
|
||||
const storedGroups = group.items;
|
||||
setTokenGroups(storedGroups);
|
||||
setTokensLoading(false);
|
||||
setTokenGroups(group.items);
|
||||
}
|
||||
|
||||
loadTokens();
|
||||
}, [userId, database, databaseStatus]);
|
||||
if (database && tokenGroupQuery) {
|
||||
updateTokenGroups();
|
||||
}
|
||||
}, [tokenGroupQuery, database]);
|
||||
|
||||
const getToken = useCallback(
|
||||
async (tokenId) => {
|
||||
@ -83,15 +90,15 @@ export function TokenDataProvider({ children }) {
|
||||
|
||||
const updateTokensHidden = useCallback(
|
||||
async (ids, hideInSidebar) => {
|
||||
// Update immediately to avoid UI delay
|
||||
setTokens((prevTokens) => {
|
||||
let newTokens = [...prevTokens];
|
||||
for (let id of ids) {
|
||||
const tokenIndex = newTokens.findIndex((token) => token.id === id);
|
||||
newTokens[tokenIndex].hideInSidebar = hideInSidebar;
|
||||
}
|
||||
return newTokens;
|
||||
});
|
||||
// // Update immediately to avoid UI delay
|
||||
// setTokens((prevTokens) => {
|
||||
// let newTokens = [...prevTokens];
|
||||
// for (let id of ids) {
|
||||
// const tokenIndex = newTokens.findIndex((token) => token.id === id);
|
||||
// newTokens[tokenIndex].hideInSidebar = hideInSidebar;
|
||||
// }
|
||||
// return newTokens;
|
||||
// });
|
||||
await Promise.all(
|
||||
ids.map((id) => database.table("tokens").update(id, { hideInSidebar }))
|
||||
);
|
||||
@ -108,67 +115,6 @@ export function TokenDataProvider({ children }) {
|
||||
[database]
|
||||
);
|
||||
|
||||
// Create DB observable to sync creating and deleting
|
||||
useEffect(() => {
|
||||
if (!database || databaseStatus === "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleTokenChanges(changes) {
|
||||
// Pool token changes together to call a single state update at the end
|
||||
let tokensCreated = [];
|
||||
let tokensUpdated = {};
|
||||
let tokensDeleted = [];
|
||||
for (let change of changes) {
|
||||
if (change.table === "tokens") {
|
||||
if (change.type === 1) {
|
||||
// Created
|
||||
const token = change.obj;
|
||||
tokensCreated.push(token);
|
||||
} else if (change.type === 2) {
|
||||
// Updated
|
||||
const token = change.obj;
|
||||
tokensUpdated[token.id] = token;
|
||||
} else if (change.type === 3) {
|
||||
// Deleted
|
||||
const id = change.key;
|
||||
tokensDeleted.push(id);
|
||||
}
|
||||
}
|
||||
if (change.table === "groups") {
|
||||
if (change.type === 2 && change.key === "tokens") {
|
||||
const group = applyObservableChange(change);
|
||||
const groups = group.items.filter((item) => item !== null);
|
||||
setTokenGroups(groups);
|
||||
}
|
||||
}
|
||||
}
|
||||
const tokensUpdatedArray = Object.values(tokensUpdated);
|
||||
if (
|
||||
tokensCreated.length > 0 ||
|
||||
tokensUpdatedArray.length > 0 ||
|
||||
tokensDeleted.length > 0
|
||||
) {
|
||||
setTokens((prevTokens) => {
|
||||
let newTokens = [...tokensCreated, ...prevTokens];
|
||||
for (let token of tokensUpdatedArray) {
|
||||
const tokenIndex = newTokens.findIndex((t) => t.id === token.id);
|
||||
if (tokenIndex > -1) {
|
||||
newTokens[tokenIndex] = token;
|
||||
}
|
||||
}
|
||||
return newTokens.filter((token) => !tokensDeleted.includes(token.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
database.on("changes", handleTokenChanges);
|
||||
|
||||
return () => {
|
||||
database.on("changes").unsubscribe(handleTokenChanges);
|
||||
};
|
||||
}, [database, databaseStatus]);
|
||||
|
||||
const [tokensById, setTokensById] = useState({});
|
||||
useEffect(() => {
|
||||
setTokensById(
|
||||
|
@ -1,7 +1,6 @@
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Dexie, { DexieOptions } from "dexie";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import "dexie-observable";
|
||||
|
||||
import { loadVersions } from "./upgrade";
|
||||
import { getDefaultMaps } from "./maps";
|
||||
|
@ -75,6 +75,9 @@ export function groupBy(array, key) {
|
||||
}
|
||||
|
||||
export const isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
||||
export const isSafari = /^((?!chrome|android).)*safari/i.test(
|
||||
navigator.userAgent
|
||||
);
|
||||
|
||||
export function shuffle(array) {
|
||||
let temp = [...array];
|
||||
|
@ -81,6 +81,7 @@ function ImportExportModal({ isOpen, onRequestClose }) {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.error(e);
|
||||
setError(e);
|
||||
}
|
||||
}
|
||||
@ -233,7 +234,7 @@ function ImportExportModal({ isOpen, onRequestClose }) {
|
||||
.bulkGet(Object.keys(newAssetIds));
|
||||
let assets = [];
|
||||
for (let asset of assetsToAdd) {
|
||||
assets.push({ ...asset, id: newAssetIds[asset.id] });
|
||||
assets.push({ ...asset, id: newAssetIds[asset.id], owner: userId });
|
||||
}
|
||||
await db.table("assets").bulkAdd(assets);
|
||||
|
||||
|
140
src/upgrade.js
140
src/upgrade.js
@ -3,6 +3,7 @@ import Dexie, { Version } from "dexie";
|
||||
import shortid from "shortid";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import Case from "case";
|
||||
import chunk from "lodash.chunk";
|
||||
|
||||
import blobToBuffer from "./helpers/blobToBuffer";
|
||||
import { getGridDefaultInset } from "./helpers/grid";
|
||||
@ -475,10 +476,19 @@ export const versions = {
|
||||
},
|
||||
// v1.9.0 - Move map assets into new table
|
||||
24(v, onUpgrade) {
|
||||
v.stores({ assets: "id, owner" }).upgrade((tx) => {
|
||||
v.stores({ assets: "id, owner" }).upgrade(async (tx) => {
|
||||
onUpgrade?.(24);
|
||||
tx.table("maps").each((map) => {
|
||||
|
||||
const primaryKeys = await Dexie.waitFor(
|
||||
tx.table("maps").toCollection().primaryKeys()
|
||||
);
|
||||
const keyChunks = chunk(primaryKeys, 4);
|
||||
|
||||
for (let keys of keyChunks) {
|
||||
let assets = [];
|
||||
let maps = await Dexie.waitFor(tx.table("maps").bulkGet(keys));
|
||||
while (maps.length > 0) {
|
||||
const map = maps.pop();
|
||||
assets.push({
|
||||
id: uuid(),
|
||||
owner: map.owner,
|
||||
@ -515,17 +525,28 @@ export const versions = {
|
||||
prevId: map.id,
|
||||
prevType: "mapThumbnail",
|
||||
});
|
||||
|
||||
tx.table("assets").bulkAdd(assets);
|
||||
});
|
||||
}
|
||||
maps = null;
|
||||
await tx.table("assets").bulkAdd(assets);
|
||||
assets = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
// v1.9.0 - Move token assets into new table
|
||||
25(v, onUpgrade) {
|
||||
v.stores({}).upgrade((tx) => {
|
||||
v.stores({}).upgrade(async (tx) => {
|
||||
onUpgrade?.(25);
|
||||
tx.table("tokens").each((token) => {
|
||||
|
||||
const primaryKeys = await Dexie.waitFor(
|
||||
tx.table("tokens").toCollection().primaryKeys()
|
||||
);
|
||||
const keyChunks = chunk(primaryKeys, 4);
|
||||
|
||||
for (let keys of keyChunks) {
|
||||
let assets = [];
|
||||
let tokens = await Dexie.waitFor(tx.table("tokens").bulkGet(keys));
|
||||
while (tokens.length > 0) {
|
||||
let token = tokens.pop();
|
||||
assets.push({
|
||||
id: uuid(),
|
||||
owner: token.owner,
|
||||
@ -546,32 +567,75 @@ export const versions = {
|
||||
prevId: token.id,
|
||||
prevType: "tokenThumbnail",
|
||||
});
|
||||
tx.table("assets").bulkAdd(assets);
|
||||
});
|
||||
}
|
||||
tokens = null;
|
||||
await tx.table("assets").bulkAdd(assets);
|
||||
assets = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
// v1.9.0 - Create foreign keys for assets
|
||||
26(v, onUpgrade) {
|
||||
v.stores({}).upgrade((tx) => {
|
||||
v.stores({}).upgrade(async (tx) => {
|
||||
onUpgrade?.(26);
|
||||
tx.table("assets").each((asset) => {
|
||||
if (asset.prevType === "map") {
|
||||
tx.table("maps").update(asset.prevId, {
|
||||
file: asset.id,
|
||||
});
|
||||
} else if (asset.prevType === "token") {
|
||||
tx.table("tokens").update(asset.prevId, {
|
||||
file: asset.id,
|
||||
});
|
||||
} else if (asset.prevType === "mapThumbnail") {
|
||||
tx.table("maps").update(asset.prevId, { thumbnail: asset.id });
|
||||
} else if (asset.prevType === "tokenThumbnail") {
|
||||
tx.table("tokens").update(asset.prevId, { thumbnail: asset.id });
|
||||
} else if (asset.prevType === "mapResolution") {
|
||||
tx.table("maps").update(asset.prevId, {
|
||||
resolutions: undefined,
|
||||
[asset.resolution]: asset.id,
|
||||
|
||||
let mapUpdates = {};
|
||||
let tokenUpdates = {};
|
||||
|
||||
const primaryKeys = await Dexie.waitFor(
|
||||
tx.table("assets").toCollection().primaryKeys()
|
||||
);
|
||||
const keyChunks = chunk(primaryKeys, 4);
|
||||
|
||||
for (let keys of keyChunks) {
|
||||
let assets = await Dexie.waitFor(tx.table("assets").bulkGet(keys));
|
||||
while (assets.length > 0) {
|
||||
const asset = assets.pop();
|
||||
const { prevId, id, prevType, resolution } = asset;
|
||||
if (prevType === "token" || prevType === "tokenThumbnail") {
|
||||
if (!(prevId in tokenUpdates)) {
|
||||
tokenUpdates[prevId] = {};
|
||||
}
|
||||
} else {
|
||||
if (!(prevId in mapUpdates)) {
|
||||
mapUpdates[prevId] = {};
|
||||
}
|
||||
}
|
||||
|
||||
if (prevType === "map") {
|
||||
mapUpdates[prevId].file = id;
|
||||
} else if (prevType === "token") {
|
||||
tokenUpdates[prevId].file = id;
|
||||
} else if (prevType === "mapThumbnail") {
|
||||
mapUpdates[prevId].thumbnail = id;
|
||||
} else if (prevType === "tokenThumbnail") {
|
||||
tokenUpdates[prevId].thumbnail = id;
|
||||
} else if (prevType === "mapResolution") {
|
||||
mapUpdates[prevId][resolution] = id;
|
||||
}
|
||||
}
|
||||
assets = null;
|
||||
}
|
||||
|
||||
await tx
|
||||
.table("maps")
|
||||
.toCollection()
|
||||
.modify((map) => {
|
||||
if (map.id in mapUpdates) {
|
||||
for (let key in mapUpdates[map.id]) {
|
||||
map[key] = mapUpdates[map.id][key];
|
||||
}
|
||||
}
|
||||
delete map.resolutions;
|
||||
});
|
||||
await tx
|
||||
.table("tokens")
|
||||
.toCollection()
|
||||
.modify((token) => {
|
||||
if (token.id in tokenUpdates) {
|
||||
for (let key in tokenUpdates[token.id]) {
|
||||
token[key] = tokenUpdates[token.id][key];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -580,14 +644,10 @@ export const versions = {
|
||||
27(v, onUpgrade) {
|
||||
v.stores({}).upgrade((tx) => {
|
||||
onUpgrade?.(27);
|
||||
tx.table("assets")
|
||||
.toCollection()
|
||||
.modify((asset) => {
|
||||
delete asset.prevId;
|
||||
if (asset.prevType === "mapResolution") {
|
||||
delete asset.resolution;
|
||||
}
|
||||
delete asset.prevType;
|
||||
tx.table("assets").toCollection().modify({
|
||||
prevId: undefined,
|
||||
prevType: undefined,
|
||||
resolution: undefined,
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -771,9 +831,17 @@ export const versions = {
|
||||
});
|
||||
});
|
||||
},
|
||||
36(v) {
|
||||
v.stores({
|
||||
_changes: null,
|
||||
_intercomm: null,
|
||||
_syncNodes: null,
|
||||
_uncommittedChanges: null,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const latestVersion = 35;
|
||||
export const latestVersion = 36;
|
||||
|
||||
/**
|
||||
* Load versions onto a database up to a specific version number
|
||||
|
@ -8,6 +8,7 @@ import { encode, decode } from "@msgpack/msgpack";
|
||||
|
||||
import { getDatabase } from "../database";
|
||||
import blobToBuffer from "../helpers/blobToBuffer";
|
||||
import { isSafari } from "../helpers/shared";
|
||||
|
||||
// Worker to load large amounts of database data on a separate thread
|
||||
let service = {
|
||||
@ -45,6 +46,10 @@ let service = {
|
||||
* @param {string} table
|
||||
*/
|
||||
async putData(data, table) {
|
||||
if (isSafari) {
|
||||
// Safari is unable to put data into indexedb and have useLiveQuery update
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
let db = getDatabase({});
|
||||
const decoded = decode(data);
|
||||
|
20
yarn.lock
20
yarn.lock
@ -5369,12 +5369,17 @@ detect-port-alt@1.1.6:
|
||||
address "^1.0.1"
|
||||
debug "^2.6.0"
|
||||
|
||||
dexie-observable@^3.0.0-beta.10:
|
||||
version "3.0.0-beta.10"
|
||||
resolved "https://registry.yarnpkg.com/dexie-observable/-/dexie-observable-3.0.0-beta.10.tgz#ad7a7e136defbb62f9eab9198a5cb9e10bce1c87"
|
||||
integrity sha512-GMPwQMLh1nYqM1MYsOZudsIwSMqDMrAOBxNuw+Y2ijsrQTBPi3nRF2CinY02IdlmffkaU7DsDfnlgdaMEaiHTQ==
|
||||
dexie-react-hooks@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/dexie-react-hooks/-/dexie-react-hooks-1.0.6.tgz#a21d116addb2bb785507bf924cc6e963d858d5d5"
|
||||
integrity sha512-OFoOBC4BQzkVGicuWl/cIMtlPp0wTAnUXwUJzq+l/zp0XVGmwEWkemRFq7JbudJLT0DINFVVzgVhGV7KOUK7uA==
|
||||
|
||||
"dexie@^3.0.0-alpha.5 || ^2.0.4", dexie@^3.0.3:
|
||||
dexie@3.1.0-beta.13:
|
||||
version "3.1.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.1.0-beta.13.tgz#54b3438e2aca3b60f87a823a535ce1b4313056ec"
|
||||
integrity sha512-pUcX9YyX1VDjF1oMqiOys6N2zoXIA/CeTghB3P4Ee77U8n9q0qa2pmNYoHyyYPKLU58+gzsMJuOc6HLPJDQrQQ==
|
||||
|
||||
"dexie@^3.0.0-alpha.5 || ^2.0.4":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.0.3.tgz#ede63849dfe5f07e13e99bb72a040e8ac1d29dab"
|
||||
integrity sha512-BSFhGpngnCl1DOr+8YNwBDobRMH0ziJs2vts69VilwetHYOtEDcLqo7d/XiIphM0tJZ2rPPyAGd31lgH2Ln3nw==
|
||||
@ -8599,6 +8604,11 @@ lodash.camelcase@^4.3.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
||||
|
||||
lodash.chunk@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc"
|
||||
integrity sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
|
Loading…
Reference in New Issue
Block a user