commit
9e8898f769
@ -21,8 +21,8 @@
|
|||||||
"color": "^3.1.3",
|
"color": "^3.1.3",
|
||||||
"comlink": "^4.3.0",
|
"comlink": "^4.3.0",
|
||||||
"deep-diff": "^1.0.2",
|
"deep-diff": "^1.0.2",
|
||||||
"dexie": "^3.0.3",
|
"dexie": "3.1.0-beta.13",
|
||||||
"dexie-observable": "^3.0.0-beta.10",
|
"dexie-react-hooks": "^1.0.6",
|
||||||
"err-code": "^3.0.1",
|
"err-code": "^3.0.1",
|
||||||
"fake-indexeddb": "^3.1.2",
|
"fake-indexeddb": "^3.1.2",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
@ -30,6 +30,7 @@
|
|||||||
"image-outline": "^0.1.0",
|
"image-outline": "^0.1.0",
|
||||||
"intersection-observer": "^0.12.0",
|
"intersection-observer": "^0.12.0",
|
||||||
"konva": "^7.2.5",
|
"konva": "^7.2.5",
|
||||||
|
"lodash.chunk": "^4.2.0",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.set": "^4.3.2",
|
"lodash.set": "^4.3.2",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useContext, useCallback, useEffect } from "react";
|
import React, { useState, useContext, useCallback, useEffect } from "react";
|
||||||
import * as Comlink from "comlink";
|
import * as Comlink from "comlink";
|
||||||
import { encode } from "@msgpack/msgpack";
|
import { encode } from "@msgpack/msgpack";
|
||||||
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
|
|
||||||
import { useDatabase } from "./DatabaseContext";
|
import { useDatabase } from "./DatabaseContext";
|
||||||
|
|
||||||
@ -128,6 +129,47 @@ export const AssetURLsUpdaterContext = React.createContext();
|
|||||||
*/
|
*/
|
||||||
export function AssetURLsProvider({ children }) {
|
export function AssetURLsProvider({ children }) {
|
||||||
const [assetURLs, setAssetURLs] = useState({});
|
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
|
// Clean up asset URLs every minute
|
||||||
const debouncedAssetURLs = useDebounce(assetURLs, 60 * 1000);
|
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");
|
throw new Error("useAssetURL must be used within a AssetURLsProvider");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getAsset } = useAssets();
|
|
||||||
const { database, databaseStatus } = useDatabase();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (!assetId || type !== "file") {
|
||||||
!assetId ||
|
|
||||||
type !== "file" ||
|
|
||||||
!database ||
|
|
||||||
databaseStatus === "loading"
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,13 +235,10 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createURL(prevURLs, asset) {
|
function createReference(prevURLs) {
|
||||||
const url = URL.createObjectURL(
|
|
||||||
new Blob([asset.file], { type: asset.mime })
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
...prevURLs,
|
...prevURLs,
|
||||||
[assetId]: { url, id: assetId, references: 1 },
|
[assetId]: { url: null, id: assetId, references: 1 },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
setAssetURLs((prevURLs) => {
|
setAssetURLs((prevURLs) => {
|
||||||
@ -215,59 +246,14 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) {
|
|||||||
// Check if the asset url is already added and increase references
|
// Check if the asset url is already added and increase references
|
||||||
return increaseReferences(prevURLs);
|
return increaseReferences(prevURLs);
|
||||||
} else {
|
} else {
|
||||||
getAsset(assetId).then((asset) => {
|
return createReference(prevURLs);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAssetURL();
|
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 () => {
|
return () => {
|
||||||
database.on("changes").unsubscribe(handleAssetChanges);
|
|
||||||
|
|
||||||
// Decrease references
|
// Decrease references
|
||||||
setAssetURLs((prevURLs) => {
|
setAssetURLs((prevURLs) => {
|
||||||
if (assetId in 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) {
|
if (!assetId) {
|
||||||
return unknownSource;
|
return unknownSource;
|
||||||
|
@ -40,7 +40,7 @@ export function DatabaseProvider({ children }) {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true,
|
||||||
(v) => {
|
() => {
|
||||||
setDatabaseStatus("upgrading");
|
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 { useDatabase } from "./DatabaseContext";
|
||||||
|
|
||||||
import { applyObservableChange } from "../helpers/dexie";
|
|
||||||
import { removeGroupsItems } from "../helpers/group";
|
import { removeGroupsItems } from "../helpers/group";
|
||||||
|
|
||||||
const MapDataContext = React.createContext();
|
const MapDataContext = React.createContext();
|
||||||
@ -18,33 +23,39 @@ const defaultMapState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function MapDataProvider({ children }) {
|
export function MapDataProvider({ children }) {
|
||||||
const { database, databaseStatus } = useDatabase();
|
const { database } = useDatabase();
|
||||||
const userId = useUserId();
|
|
||||||
|
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([]);
|
const [mapGroups, setMapGroups] = useState([]);
|
||||||
|
|
||||||
// Load maps from the database and ensure state is properly setup
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userId || !database || databaseStatus === "loading") {
|
async function updateMapGroups() {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadMaps() {
|
|
||||||
const storedMaps = await database.table("maps").toArray();
|
|
||||||
setMaps(storedMaps);
|
|
||||||
const storedStates = await database.table("states").toArray();
|
|
||||||
setMapStates(storedStates);
|
|
||||||
const group = await database.table("groups").get("maps");
|
const group = await database.table("groups").get("maps");
|
||||||
const storedGroups = group.items;
|
setMapGroups(group.items);
|
||||||
setMapGroups(storedGroups);
|
|
||||||
setMapsLoading(false);
|
|
||||||
}
|
}
|
||||||
|
if (database && mapGroupQuery) {
|
||||||
loadMaps();
|
updateMapGroups();
|
||||||
}, [userId, database, databaseStatus]);
|
}
|
||||||
|
}, [mapGroupQuery, database]);
|
||||||
|
|
||||||
const getMap = useCallback(
|
const getMap = useCallback(
|
||||||
async (mapId) => {
|
async (mapId) => {
|
||||||
@ -138,77 +149,6 @@ export function MapDataProvider({ children }) {
|
|||||||
[database]
|
[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({});
|
const [mapsById, setMapsById] = useState({});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMapsById(
|
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 { useDatabase } from "./DatabaseContext";
|
||||||
|
|
||||||
import { applyObservableChange } from "../helpers/dexie";
|
|
||||||
import { removeGroupsItems } from "../helpers/group";
|
import { removeGroupsItems } from "../helpers/group";
|
||||||
|
|
||||||
const TokenDataContext = React.createContext();
|
const TokenDataContext = React.createContext();
|
||||||
|
|
||||||
export function TokenDataProvider({ children }) {
|
export function TokenDataProvider({ children }) {
|
||||||
const { database, databaseStatus } = useDatabase();
|
const { database } = useDatabase();
|
||||||
const userId = useUserId();
|
|
||||||
|
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([]);
|
const [tokenGroups, setTokenGroups] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userId || !database || databaseStatus === "loading") {
|
async function updateTokenGroups() {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadTokens() {
|
|
||||||
const storedTokens = await database.table("tokens").toArray();
|
|
||||||
setTokens(storedTokens);
|
|
||||||
const group = await database.table("groups").get("tokens");
|
const group = await database.table("groups").get("tokens");
|
||||||
const storedGroups = group.items;
|
setTokenGroups(group.items);
|
||||||
setTokenGroups(storedGroups);
|
|
||||||
setTokensLoading(false);
|
|
||||||
}
|
}
|
||||||
|
if (database && tokenGroupQuery) {
|
||||||
loadTokens();
|
updateTokenGroups();
|
||||||
}, [userId, database, databaseStatus]);
|
}
|
||||||
|
}, [tokenGroupQuery, database]);
|
||||||
|
|
||||||
const getToken = useCallback(
|
const getToken = useCallback(
|
||||||
async (tokenId) => {
|
async (tokenId) => {
|
||||||
@ -83,15 +90,15 @@ export function TokenDataProvider({ children }) {
|
|||||||
|
|
||||||
const updateTokensHidden = useCallback(
|
const updateTokensHidden = useCallback(
|
||||||
async (ids, hideInSidebar) => {
|
async (ids, hideInSidebar) => {
|
||||||
// Update immediately to avoid UI delay
|
// // Update immediately to avoid UI delay
|
||||||
setTokens((prevTokens) => {
|
// setTokens((prevTokens) => {
|
||||||
let newTokens = [...prevTokens];
|
// let newTokens = [...prevTokens];
|
||||||
for (let id of ids) {
|
// for (let id of ids) {
|
||||||
const tokenIndex = newTokens.findIndex((token) => token.id === id);
|
// const tokenIndex = newTokens.findIndex((token) => token.id === id);
|
||||||
newTokens[tokenIndex].hideInSidebar = hideInSidebar;
|
// newTokens[tokenIndex].hideInSidebar = hideInSidebar;
|
||||||
}
|
// }
|
||||||
return newTokens;
|
// return newTokens;
|
||||||
});
|
// });
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
ids.map((id) => database.table("tokens").update(id, { hideInSidebar }))
|
ids.map((id) => database.table("tokens").update(id, { hideInSidebar }))
|
||||||
);
|
);
|
||||||
@ -108,67 +115,6 @@ export function TokenDataProvider({ children }) {
|
|||||||
[database]
|
[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({});
|
const [tokensById, setTokensById] = useState({});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTokensById(
|
setTokensById(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import Dexie, { DexieOptions } from "dexie";
|
import Dexie, { DexieOptions } from "dexie";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import "dexie-observable";
|
|
||||||
|
|
||||||
import { loadVersions } from "./upgrade";
|
import { loadVersions } from "./upgrade";
|
||||||
import { getDefaultMaps } from "./maps";
|
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 isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
||||||
|
export const isSafari = /^((?!chrome|android).)*safari/i.test(
|
||||||
|
navigator.userAgent
|
||||||
|
);
|
||||||
|
|
||||||
export function shuffle(array) {
|
export function shuffle(array) {
|
||||||
let temp = [...array];
|
let temp = [...array];
|
||||||
|
@ -81,6 +81,7 @@ function ImportExportModal({ isOpen, onRequestClose }) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
console.error(e);
|
||||||
setError(e);
|
setError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,7 +234,7 @@ function ImportExportModal({ isOpen, onRequestClose }) {
|
|||||||
.bulkGet(Object.keys(newAssetIds));
|
.bulkGet(Object.keys(newAssetIds));
|
||||||
let assets = [];
|
let assets = [];
|
||||||
for (let asset of assetsToAdd) {
|
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);
|
await db.table("assets").bulkAdd(assets);
|
||||||
|
|
||||||
|
244
src/upgrade.js
244
src/upgrade.js
@ -3,6 +3,7 @@ import Dexie, { Version } from "dexie";
|
|||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import Case from "case";
|
import Case from "case";
|
||||||
|
import chunk from "lodash.chunk";
|
||||||
|
|
||||||
import blobToBuffer from "./helpers/blobToBuffer";
|
import blobToBuffer from "./helpers/blobToBuffer";
|
||||||
import { getGridDefaultInset } from "./helpers/grid";
|
import { getGridDefaultInset } from "./helpers/grid";
|
||||||
@ -475,120 +476,179 @@ export const versions = {
|
|||||||
},
|
},
|
||||||
// v1.9.0 - Move map assets into new table
|
// v1.9.0 - Move map assets into new table
|
||||||
24(v, onUpgrade) {
|
24(v, onUpgrade) {
|
||||||
v.stores({ assets: "id, owner" }).upgrade((tx) => {
|
v.stores({ assets: "id, owner" }).upgrade(async (tx) => {
|
||||||
onUpgrade?.(24);
|
onUpgrade?.(24);
|
||||||
tx.table("maps").each((map) => {
|
|
||||||
let assets = [];
|
|
||||||
assets.push({
|
|
||||||
id: uuid(),
|
|
||||||
owner: map.owner,
|
|
||||||
file: map.file,
|
|
||||||
width: map.width,
|
|
||||||
height: map.height,
|
|
||||||
mime: "",
|
|
||||||
prevId: map.id,
|
|
||||||
prevType: "map",
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let resolution in map.resolutions) {
|
const primaryKeys = await Dexie.waitFor(
|
||||||
const mapRes = map.resolutions[resolution];
|
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({
|
assets.push({
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
owner: map.owner,
|
owner: map.owner,
|
||||||
file: mapRes.file,
|
file: map.file,
|
||||||
width: mapRes.width,
|
width: map.width,
|
||||||
height: mapRes.height,
|
height: map.height,
|
||||||
mime: "",
|
mime: "",
|
||||||
prevId: map.id,
|
prevId: map.id,
|
||||||
prevType: "mapResolution",
|
prevType: "map",
|
||||||
resolution,
|
});
|
||||||
|
|
||||||
|
for (let resolution in map.resolutions) {
|
||||||
|
const mapRes = map.resolutions[resolution];
|
||||||
|
assets.push({
|
||||||
|
id: uuid(),
|
||||||
|
owner: map.owner,
|
||||||
|
file: mapRes.file,
|
||||||
|
width: mapRes.width,
|
||||||
|
height: mapRes.height,
|
||||||
|
mime: "",
|
||||||
|
prevId: map.id,
|
||||||
|
prevType: "mapResolution",
|
||||||
|
resolution,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.push({
|
||||||
|
id: uuid(),
|
||||||
|
owner: map.owner,
|
||||||
|
file: map.thumbnail.file,
|
||||||
|
width: map.thumbnail.width,
|
||||||
|
height: map.thumbnail.height,
|
||||||
|
mime: "",
|
||||||
|
prevId: map.id,
|
||||||
|
prevType: "mapThumbnail",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
maps = null;
|
||||||
assets.push({
|
await tx.table("assets").bulkAdd(assets);
|
||||||
id: uuid(),
|
assets = null;
|
||||||
owner: map.owner,
|
}
|
||||||
file: map.thumbnail.file,
|
|
||||||
width: map.thumbnail.width,
|
|
||||||
height: map.thumbnail.height,
|
|
||||||
mime: "",
|
|
||||||
prevId: map.id,
|
|
||||||
prevType: "mapThumbnail",
|
|
||||||
});
|
|
||||||
|
|
||||||
tx.table("assets").bulkAdd(assets);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.9.0 - Move token assets into new table
|
// v1.9.0 - Move token assets into new table
|
||||||
25(v, onUpgrade) {
|
25(v, onUpgrade) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade(async (tx) => {
|
||||||
onUpgrade?.(25);
|
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 assets = [];
|
||||||
assets.push({
|
let tokens = await Dexie.waitFor(tx.table("tokens").bulkGet(keys));
|
||||||
id: uuid(),
|
while (tokens.length > 0) {
|
||||||
owner: token.owner,
|
let token = tokens.pop();
|
||||||
file: token.file,
|
assets.push({
|
||||||
width: token.width,
|
id: uuid(),
|
||||||
height: token.height,
|
owner: token.owner,
|
||||||
mime: "",
|
file: token.file,
|
||||||
prevId: token.id,
|
width: token.width,
|
||||||
prevType: "token",
|
height: token.height,
|
||||||
});
|
mime: "",
|
||||||
assets.push({
|
prevId: token.id,
|
||||||
id: uuid(),
|
prevType: "token",
|
||||||
owner: token.owner,
|
});
|
||||||
file: token.thumbnail.file,
|
assets.push({
|
||||||
width: token.thumbnail.width,
|
id: uuid(),
|
||||||
height: token.thumbnail.height,
|
owner: token.owner,
|
||||||
mime: "",
|
file: token.thumbnail.file,
|
||||||
prevId: token.id,
|
width: token.thumbnail.width,
|
||||||
prevType: "tokenThumbnail",
|
height: token.thumbnail.height,
|
||||||
});
|
mime: "",
|
||||||
tx.table("assets").bulkAdd(assets);
|
prevId: token.id,
|
||||||
});
|
prevType: "tokenThumbnail",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tokens = null;
|
||||||
|
await tx.table("assets").bulkAdd(assets);
|
||||||
|
assets = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.9.0 - Create foreign keys for assets
|
// v1.9.0 - Create foreign keys for assets
|
||||||
26(v, onUpgrade) {
|
26(v, onUpgrade) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade(async (tx) => {
|
||||||
onUpgrade?.(26);
|
onUpgrade?.(26);
|
||||||
tx.table("assets").each((asset) => {
|
|
||||||
if (asset.prevType === "map") {
|
let mapUpdates = {};
|
||||||
tx.table("maps").update(asset.prevId, {
|
let tokenUpdates = {};
|
||||||
file: asset.id,
|
|
||||||
});
|
const primaryKeys = await Dexie.waitFor(
|
||||||
} else if (asset.prevType === "token") {
|
tx.table("assets").toCollection().primaryKeys()
|
||||||
tx.table("tokens").update(asset.prevId, {
|
);
|
||||||
file: asset.id,
|
const keyChunks = chunk(primaryKeys, 4);
|
||||||
});
|
|
||||||
} else if (asset.prevType === "mapThumbnail") {
|
for (let keys of keyChunks) {
|
||||||
tx.table("maps").update(asset.prevId, { thumbnail: asset.id });
|
let assets = await Dexie.waitFor(tx.table("assets").bulkGet(keys));
|
||||||
} else if (asset.prevType === "tokenThumbnail") {
|
while (assets.length > 0) {
|
||||||
tx.table("tokens").update(asset.prevId, { thumbnail: asset.id });
|
const asset = assets.pop();
|
||||||
} else if (asset.prevType === "mapResolution") {
|
const { prevId, id, prevType, resolution } = asset;
|
||||||
tx.table("maps").update(asset.prevId, {
|
if (prevType === "token" || prevType === "tokenThumbnail") {
|
||||||
resolutions: undefined,
|
if (!(prevId in tokenUpdates)) {
|
||||||
[asset.resolution]: asset.id,
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.9.0 - Remove asset migration helpers
|
// v1.9.0 - Remove asset migration helpers
|
||||||
27(v, onUpgrade) {
|
27(v, onUpgrade) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx) => {
|
||||||
onUpgrade?.(27);
|
onUpgrade?.(27);
|
||||||
tx.table("assets")
|
tx.table("assets").toCollection().modify({
|
||||||
.toCollection()
|
prevId: undefined,
|
||||||
.modify((asset) => {
|
prevType: undefined,
|
||||||
delete asset.prevId;
|
resolution: undefined,
|
||||||
if (asset.prevType === "mapResolution") {
|
});
|
||||||
delete asset.resolution;
|
|
||||||
}
|
|
||||||
delete asset.prevType;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.9.0 - Remap map resolution assets
|
// v1.9.0 - Remap map resolution assets
|
||||||
@ -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
|
* 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 { getDatabase } from "../database";
|
||||||
import blobToBuffer from "../helpers/blobToBuffer";
|
import blobToBuffer from "../helpers/blobToBuffer";
|
||||||
|
import { isSafari } from "../helpers/shared";
|
||||||
|
|
||||||
// Worker to load large amounts of database data on a separate thread
|
// Worker to load large amounts of database data on a separate thread
|
||||||
let service = {
|
let service = {
|
||||||
@ -45,6 +46,10 @@ let service = {
|
|||||||
* @param {string} table
|
* @param {string} table
|
||||||
*/
|
*/
|
||||||
async putData(data, table) {
|
async putData(data, table) {
|
||||||
|
if (isSafari) {
|
||||||
|
// Safari is unable to put data into indexedb and have useLiveQuery update
|
||||||
|
return false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
let db = getDatabase({});
|
let db = getDatabase({});
|
||||||
const decoded = decode(data);
|
const decoded = decode(data);
|
||||||
|
22
yarn.lock
22
yarn.lock
@ -5369,12 +5369,17 @@ detect-port-alt@1.1.6:
|
|||||||
address "^1.0.1"
|
address "^1.0.1"
|
||||||
debug "^2.6.0"
|
debug "^2.6.0"
|
||||||
|
|
||||||
dexie-observable@^3.0.0-beta.10:
|
dexie-react-hooks@^1.0.6:
|
||||||
version "3.0.0-beta.10"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/dexie-observable/-/dexie-observable-3.0.0-beta.10.tgz#ad7a7e136defbb62f9eab9198a5cb9e10bce1c87"
|
resolved "https://registry.yarnpkg.com/dexie-react-hooks/-/dexie-react-hooks-1.0.6.tgz#a21d116addb2bb785507bf924cc6e963d858d5d5"
|
||||||
integrity sha512-GMPwQMLh1nYqM1MYsOZudsIwSMqDMrAOBxNuw+Y2ijsrQTBPi3nRF2CinY02IdlmffkaU7DsDfnlgdaMEaiHTQ==
|
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"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.0.3.tgz#ede63849dfe5f07e13e99bb72a040e8ac1d29dab"
|
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.0.3.tgz#ede63849dfe5f07e13e99bb72a040e8ac1d29dab"
|
||||||
integrity sha512-BSFhGpngnCl1DOr+8YNwBDobRMH0ziJs2vts69VilwetHYOtEDcLqo7d/XiIphM0tJZ2rPPyAGd31lgH2Ln3nw==
|
integrity sha512-BSFhGpngnCl1DOr+8YNwBDobRMH0ziJs2vts69VilwetHYOtEDcLqo7d/XiIphM0tJZ2rPPyAGd31lgH2Ln3nw==
|
||||||
@ -7202,7 +7207,7 @@ image-outline@^0.1.0:
|
|||||||
marching-squares "^0.2.0"
|
marching-squares "^0.2.0"
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
ndarray "^1.0.18"
|
ndarray "^1.0.18"
|
||||||
|
|
||||||
immediate@~3.0.5:
|
immediate@~3.0.5:
|
||||||
version "3.0.6"
|
version "3.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||||
@ -8599,6 +8604,11 @@ lodash.camelcase@^4.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
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:
|
lodash.clonedeep@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||||
|
Loading…
Reference in New Issue
Block a user