From 097df533dd5269884e9f1d24dc5f8f1a72c1210f Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 25 Jun 2021 17:43:43 +1000 Subject: [PATCH 1/8] Remove unused variable --- src/contexts/DatabaseContext.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contexts/DatabaseContext.js b/src/contexts/DatabaseContext.js index 64714ee..92a8b90 100644 --- a/src/contexts/DatabaseContext.js +++ b/src/contexts/DatabaseContext.js @@ -40,7 +40,7 @@ export function DatabaseProvider({ children }) { undefined, undefined, true, - (v) => { + () => { setDatabaseStatus("upgrading"); } ); From 45ce2685c6be91c4bed1496edbd53cf233c4dc98 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 25 Jun 2021 17:43:55 +1000 Subject: [PATCH 2/8] Add console error to import --- src/modals/ImportExportModal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modals/ImportExportModal.js b/src/modals/ImportExportModal.js index b0f92e4..fad4fda 100644 --- a/src/modals/ImportExportModal.js +++ b/src/modals/ImportExportModal.js @@ -81,6 +81,7 @@ function ImportExportModal({ isOpen, onRequestClose }) { ) ); } else { + console.error(e); setError(e); } } From 3b8565ad54ce82a55071009206be38b3aa7c3d05 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 25 Jun 2021 17:45:14 +1000 Subject: [PATCH 3/8] Replace dexie observable with useLiveQuery --- package.json | 4 +- src/contexts/AssetsContext.js | 31 -------- src/contexts/MapDataContext.js | 132 +++++++++---------------------- src/contexts/TokenDataContext.js | 128 +++++++++--------------------- src/database.js | 1 - yarn.lock | 17 ++-- 6 files changed, 86 insertions(+), 227 deletions(-) diff --git a/package.json b/package.json index 562ab0b..c474470 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/contexts/AssetsContext.js b/src/contexts/AssetsContext.js index c1828a5..46cb63d 100644 --- a/src/contexts/AssetsContext.js +++ b/src/contexts/AssetsContext.js @@ -236,38 +236,7 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) { 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) { diff --git a/src/contexts/MapDataContext.js b/src/contexts/MapDataContext.js index ebf95bc..f33cf98 100644 --- a/src/contexts/MapDataContext.js +++ b/src/contexts/MapDataContext.js @@ -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( diff --git a/src/contexts/TokenDataContext.js b/src/contexts/TokenDataContext.js index b4bd33c..a805ac5 100644 --- a/src/contexts/TokenDataContext.js +++ b/src/contexts/TokenDataContext.js @@ -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( diff --git a/src/database.js b/src/database.js index a5251f1..c834c95 100644 --- a/src/database.js +++ b/src/database.js @@ -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"; diff --git a/yarn.lock b/yarn.lock index 9907962..0b96658 100644 --- a/yarn.lock +++ b/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== @@ -7202,7 +7207,7 @@ image-outline@^0.1.0: marching-squares "^0.2.0" minimist "^1.2.0" ndarray "^1.0.18" - + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" From d8bad364434b6d725f3cecbb337184f2f3d1c727 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 25 Jun 2021 17:45:49 +1000 Subject: [PATCH 4/8] Chunk 1.9.0 db upgrade and remove observable tables --- package.json | 1 + src/upgrade.js | 244 +++++++++++++++++++++++++++++++------------------ yarn.lock | 5 + 3 files changed, 162 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index c474470..f573da7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/upgrade.js b/src/upgrade.js index eb3f554..a3fb0be 100644 --- a/src/upgrade.js +++ b/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,120 +476,179 @@ 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) => { - 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 mapRes = map.resolutions[resolution]; + 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, - file: mapRes.file, - width: mapRes.width, - height: mapRes.height, + file: map.file, + width: map.width, + height: map.height, mime: "", prevId: map.id, - prevType: "mapResolution", - resolution, + prevType: "map", + }); + + 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", }); } - - 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", - }); - - 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 = []; - assets.push({ - id: uuid(), - owner: token.owner, - file: token.file, - width: token.width, - height: token.height, - mime: "", - prevId: token.id, - prevType: "token", - }); - assets.push({ - id: uuid(), - owner: token.owner, - file: token.thumbnail.file, - width: token.thumbnail.width, - height: token.thumbnail.height, - mime: "", - prevId: token.id, - prevType: "tokenThumbnail", - }); - tx.table("assets").bulkAdd(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, + file: token.file, + width: token.width, + height: token.height, + mime: "", + prevId: token.id, + prevType: "token", + }); + assets.push({ + id: uuid(), + owner: token.owner, + file: token.thumbnail.file, + width: token.thumbnail.width, + height: token.thumbnail.height, + mime: "", + prevId: token.id, + prevType: "tokenThumbnail", + }); + } + 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]; + } + } + }); }); }, // v1.9.0 - Remove asset migration helpers 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, + }); }); }, // 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 diff --git a/yarn.lock b/yarn.lock index 0b96658..bb83ecf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8604,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" From cb48e6f946c3bb8aa93be82a86fafbc3d594e7ae Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 25 Jun 2021 18:59:59 +1000 Subject: [PATCH 5/8] Fix assets not showing up to players without a refresh --- src/contexts/AssetsContext.js | 69 +++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/src/contexts/AssetsContext.js b/src/contexts/AssetsContext.js index 46cb63d..19d8a18 100644 --- a/src/contexts/AssetsContext.js +++ b/src/contexts/AssetsContext.js @@ -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,41 @@ 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(new Set()); + useEffect(() => { + const keys = Object.keys(assetURLs); + let newKeys = keys.filter((key) => !assetKeys.has(key)); + if (newKeys.length > 0) { + setAssetKeys((prevKeys) => new Set([...prevKeys, ...newKeys])); + } + }, [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) { + 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); @@ -178,15 +214,9 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) { } const { getAsset } = useAssets(); - const { database, databaseStatus } = useDatabase(); useEffect(() => { - if ( - !assetId || - type !== "file" || - !database || - databaseStatus === "loading" - ) { + if (!assetId || type !== "file") { return; } @@ -201,13 +231,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,21 +242,7 @@ 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); } }); } @@ -252,7 +265,7 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) { } }); }; - }, [assetId, setAssetURLs, getAsset, type, database, databaseStatus]); + }, [assetId, setAssetURLs, getAsset, type]); if (!assetId) { return unknownSource; From 8b1d208a72d09c00f170aacf95b4092f4af1f9b4 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 25 Jun 2021 19:50:29 +1000 Subject: [PATCH 6/8] Fix maps not loading in safari --- src/helpers/shared.js | 3 +++ src/workers/DatabaseWorker.js | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/helpers/shared.js b/src/helpers/shared.js index 5940429..b45ce17 100644 --- a/src/helpers/shared.js +++ b/src/helpers/shared.js @@ -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]; diff --git a/src/workers/DatabaseWorker.js b/src/workers/DatabaseWorker.js index 8b53785..7003e93 100644 --- a/src/workers/DatabaseWorker.js +++ b/src/workers/DatabaseWorker.js @@ -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); From 90aa43dbf0d60d024366311bbd0e31ed3bb80a8b Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 25 Jun 2021 20:38:52 +1000 Subject: [PATCH 7/8] Fix bug with cleaned up assets not reloading --- src/contexts/AssetsContext.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/contexts/AssetsContext.js b/src/contexts/AssetsContext.js index 19d8a18..90962df 100644 --- a/src/contexts/AssetsContext.js +++ b/src/contexts/AssetsContext.js @@ -132,18 +132,21 @@ export function AssetURLsProvider({ children }) { const { database } = useDatabase(); // Keep track of when the asset keys change so we can update the URLs - const [assetKeys, setAssetKeys] = useState(new Set()); + const [assetKeys, setAssetKeys] = useState([]); useEffect(() => { const keys = Object.keys(assetURLs); - let newKeys = keys.filter((key) => !assetKeys.has(key)); - if (newKeys.length > 0) { - setAssetKeys((prevKeys) => new Set([...prevKeys, ...newKeys])); + 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?.table("assets").where("id").anyOf(assetKeys).toArray(), [database, assetKeys] ); @@ -155,10 +158,13 @@ export function AssetURLsProvider({ children }) { setAssetURLs((prevURLs) => { let newURLs = { ...prevURLs }; for (let asset of assets) { - if (!newURLs[asset.id].url) { - newURLs[asset.id].url = URL.createObjectURL( - new Blob([asset.file], { type: asset.mime }) - ); + if (newURLs[asset.id].url === null) { + newURLs[asset.id] = { + ...newURLs[asset.id], + url: URL.createObjectURL( + new Blob([asset.file], { type: asset.mime }) + ), + }; } } return newURLs; @@ -213,8 +219,6 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) { throw new Error("useAssetURL must be used within a AssetURLsProvider"); } - const { getAsset } = useAssets(); - useEffect(() => { if (!assetId || type !== "file") { return; @@ -265,7 +269,7 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource) { } }); }; - }, [assetId, setAssetURLs, getAsset, type]); + }, [assetId, setAssetURLs, type]); if (!assetId) { return unknownSource; From 9d587cb2b15fe24024efe3fc67d5e6c9ccd5ad8d Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 25 Jun 2021 20:39:12 +1000 Subject: [PATCH 8/8] Fix bug with imported assets not getting assigned ownership --- src/modals/ImportExportModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modals/ImportExportModal.js b/src/modals/ImportExportModal.js index fad4fda..ae9c5c8 100644 --- a/src/modals/ImportExportModal.js +++ b/src/modals/ImportExportModal.js @@ -234,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);