Moved assets into new table in the database
This commit is contained in:
parent
d620463c15
commit
9f11161b23
@ -57,6 +57,7 @@
|
|||||||
"source-map-explorer": "^2.5.2",
|
"source-map-explorer": "^2.5.2",
|
||||||
"theme-ui": "^0.3.1",
|
"theme-ui": "^0.3.1",
|
||||||
"use-image": "^1.0.7",
|
"use-image": "^1.0.7",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
"webrtc-adapter": "^7.7.1"
|
"webrtc-adapter": "^7.7.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
72
src/App.js
72
src/App.js
@ -18,7 +18,7 @@ import { TokenDataProvider } from "./contexts/TokenDataContext";
|
|||||||
import { MapLoadingProvider } from "./contexts/MapLoadingContext";
|
import { MapLoadingProvider } from "./contexts/MapLoadingContext";
|
||||||
import { SettingsProvider } from "./contexts/SettingsContext";
|
import { SettingsProvider } from "./contexts/SettingsContext";
|
||||||
import { KeyboardProvider } from "./contexts/KeyboardContext";
|
import { KeyboardProvider } from "./contexts/KeyboardContext";
|
||||||
import { ImageSourcesProvider } from "./contexts/ImageSourceContext";
|
import { AssetsProvider, AssetURLsProvider } from "./contexts/AssetsContext";
|
||||||
|
|
||||||
import { ToastProvider } from "./components/Toast";
|
import { ToastProvider } from "./components/Toast";
|
||||||
|
|
||||||
@ -30,40 +30,42 @@ function App() {
|
|||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<KeyboardProvider>
|
<KeyboardProvider>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<ImageSourcesProvider>
|
<Router>
|
||||||
<Router>
|
<Switch>
|
||||||
<Switch>
|
<Route path="/donate">
|
||||||
<Route path="/donate">
|
<Donate />
|
||||||
<Donate />
|
</Route>
|
||||||
</Route>
|
{/* Legacy support camel case routes */}
|
||||||
{/* Legacy support camel case routes */}
|
<Route path={["/howTo", "/how-to"]}>
|
||||||
<Route path={["/howTo", "/how-to"]}>
|
<HowTo />
|
||||||
<HowTo />
|
</Route>
|
||||||
</Route>
|
<Route path={["/releaseNotes", "/release-notes"]}>
|
||||||
<Route path={["/releaseNotes", "/release-notes"]}>
|
<ReleaseNotes />
|
||||||
<ReleaseNotes />
|
</Route>
|
||||||
</Route>
|
<Route path="/about">
|
||||||
<Route path="/about">
|
<About />
|
||||||
<About />
|
</Route>
|
||||||
</Route>
|
<Route path="/faq">
|
||||||
<Route path="/faq">
|
<FAQ />
|
||||||
<FAQ />
|
</Route>
|
||||||
</Route>
|
<Route path="/game/:id">
|
||||||
<Route path="/game/:id">
|
<AssetsProvider>
|
||||||
<MapLoadingProvider>
|
<AssetURLsProvider>
|
||||||
<MapDataProvider>
|
<MapLoadingProvider>
|
||||||
<TokenDataProvider>
|
<MapDataProvider>
|
||||||
<Game />
|
<TokenDataProvider>
|
||||||
</TokenDataProvider>
|
<Game />
|
||||||
</MapDataProvider>
|
</TokenDataProvider>
|
||||||
</MapLoadingProvider>
|
</MapDataProvider>
|
||||||
</Route>
|
</MapLoadingProvider>
|
||||||
<Route path="/">
|
</AssetURLsProvider>
|
||||||
<Home />
|
</AssetsProvider>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
<Route path="/">
|
||||||
</Router>
|
<Home />
|
||||||
</ImageSourcesProvider>
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</Router>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</KeyboardProvider>
|
</KeyboardProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
@ -23,7 +23,7 @@ import MapGrid from "./MapGrid";
|
|||||||
import MapGridEditor from "./MapGridEditor";
|
import MapGridEditor from "./MapGridEditor";
|
||||||
|
|
||||||
function MapEditor({ map, onSettingsChange }) {
|
function MapEditor({ map, onSettingsChange }) {
|
||||||
const [mapImageSource] = useMapImage(map);
|
const [mapImage] = useMapImage(map);
|
||||||
|
|
||||||
const [stageWidth, setStageWidth] = useState(1);
|
const [stageWidth, setStageWidth] = useState(1);
|
||||||
const [stageHeight, setStageHeight] = useState(1);
|
const [stageHeight, setStageHeight] = useState(1);
|
||||||
@ -132,11 +132,7 @@ function MapEditor({ map, onSettingsChange }) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Layer ref={mapLayerRef}>
|
<Layer ref={mapLayerRef}>
|
||||||
<Image
|
<Image image={mapImage} width={mapWidth} height={mapHeight} />
|
||||||
image={mapImageSource}
|
|
||||||
width={mapWidth}
|
|
||||||
height={mapHeight}
|
|
||||||
/>
|
|
||||||
{showGridControls && canEditGrid && (
|
{showGridControls && canEditGrid && (
|
||||||
<>
|
<>
|
||||||
<MapGrid map={map} />
|
<MapGrid map={map} />
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import useImage from "use-image";
|
import useImage from "use-image";
|
||||||
|
|
||||||
import { useImageSource } from "../../contexts/ImageSourceContext";
|
import { useDataURL } from "../../contexts/AssetsContext";
|
||||||
|
|
||||||
import { mapSources as defaultMapSources } from "../../maps";
|
import { mapSources as defaultMapSources } from "../../maps";
|
||||||
|
|
||||||
@ -13,13 +13,14 @@ function MapGrid({ map }) {
|
|||||||
let mapSourceMap = map;
|
let mapSourceMap = map;
|
||||||
// Use lowest resolution for grid lightness
|
// Use lowest resolution for grid lightness
|
||||||
if (map && map.type === "file" && map.resolutions) {
|
if (map && map.type === "file" && map.resolutions) {
|
||||||
|
// FIXME - move to resolutions array
|
||||||
const resolutionArray = Object.keys(map.resolutions);
|
const resolutionArray = Object.keys(map.resolutions);
|
||||||
if (resolutionArray.length > 0) {
|
if (resolutionArray.length > 0) {
|
||||||
mapSourceMap = map.resolutions[resolutionArray[0]];
|
mapSourceMap.quality = resolutionArray[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const mapSource = useImageSource(mapSourceMap, defaultMapSources);
|
const mapURL = useDataURL(mapSourceMap, defaultMapSources);
|
||||||
const [mapImage, mapLoadingStatus] = useImage(mapSource);
|
const [mapImage, mapLoadingStatus] = useImage(mapURL);
|
||||||
|
|
||||||
const [isImageLight, setIsImageLight] = useState(true);
|
const [isImageLight, setIsImageLight] = useState(true);
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ function MapInteraction({
|
|||||||
onSelectedToolChange,
|
onSelectedToolChange,
|
||||||
disabledControls,
|
disabledControls,
|
||||||
}) {
|
}) {
|
||||||
const [mapImageSource, mapImageSourceStatus] = useMapImage(map);
|
const [mapImage, mapImageStatus] = useMapImage(map);
|
||||||
|
|
||||||
// Map loaded taking in to account different resolutions
|
// Map loaded taking in to account different resolutions
|
||||||
const [mapLoaded, setMapLoaded] = useState(false);
|
const [mapLoaded, setMapLoaded] = useState(false);
|
||||||
@ -36,14 +36,15 @@ function MapInteraction({
|
|||||||
if (
|
if (
|
||||||
!map ||
|
!map ||
|
||||||
!mapState ||
|
!mapState ||
|
||||||
|
// FIXME
|
||||||
(map.type === "file" && !map.file && !map.resolutions) ||
|
(map.type === "file" && !map.file && !map.resolutions) ||
|
||||||
mapState.mapId !== map.id
|
mapState.mapId !== map.id
|
||||||
) {
|
) {
|
||||||
setMapLoaded(false);
|
setMapLoaded(false);
|
||||||
} else if (mapImageSourceStatus === "loaded") {
|
} else if (mapImageStatus === "loaded") {
|
||||||
setMapLoaded(true);
|
setMapLoaded(true);
|
||||||
}
|
}
|
||||||
}, [mapImageSourceStatus, map, mapState]);
|
}, [mapImageStatus, map, mapState]);
|
||||||
|
|
||||||
const [stageWidth, setStageWidth] = useState(1);
|
const [stageWidth, setStageWidth] = useState(1);
|
||||||
const [stageHeight, setStageHeight] = useState(1);
|
const [stageHeight, setStageHeight] = useState(1);
|
||||||
@ -211,7 +212,7 @@ function MapInteraction({
|
|||||||
>
|
>
|
||||||
<Layer ref={mapLayerRef}>
|
<Layer ref={mapLayerRef}>
|
||||||
<Image
|
<Image
|
||||||
image={mapLoaded && mapImageSource}
|
image={mapLoaded && mapImage}
|
||||||
width={mapWidth}
|
width={mapWidth}
|
||||||
height={mapHeight}
|
height={mapHeight}
|
||||||
id="mapImage"
|
id="mapImage"
|
||||||
|
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
|
|
||||||
import Tile from "../Tile";
|
import Tile from "../Tile";
|
||||||
|
|
||||||
import { useImageSource } from "../../contexts/ImageSourceContext";
|
import { useDataURL } from "../../contexts/AssetsContext";
|
||||||
import { mapSources as defaultMapSources, unknownSource } from "../../maps";
|
import { mapSources as defaultMapSources, unknownSource } from "../../maps";
|
||||||
|
|
||||||
function MapTile({
|
function MapTile({
|
||||||
@ -15,7 +15,7 @@ function MapTile({
|
|||||||
canEdit,
|
canEdit,
|
||||||
badges,
|
badges,
|
||||||
}) {
|
}) {
|
||||||
const mapSource = useImageSource(
|
const mapURL = useDataURL(
|
||||||
map,
|
map,
|
||||||
defaultMapSources,
|
defaultMapSources,
|
||||||
unknownSource,
|
unknownSource,
|
||||||
@ -24,7 +24,7 @@ function MapTile({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tile
|
<Tile
|
||||||
src={mapSource}
|
src={mapURL}
|
||||||
title={map.name}
|
title={map.name}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onSelect={() => onMapSelect(map)}
|
onSelect={() => onMapSelect(map)}
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
useDebouncedStageScale,
|
useDebouncedStageScale,
|
||||||
} from "../../contexts/MapInteractionContext";
|
} from "../../contexts/MapInteractionContext";
|
||||||
import { useGridCellPixelSize } from "../../contexts/GridContext";
|
import { useGridCellPixelSize } from "../../contexts/GridContext";
|
||||||
import { useImageSource } from "../../contexts/ImageSourceContext";
|
import { useDataURL } from "../../contexts/AssetsContext";
|
||||||
|
|
||||||
import TokenStatus from "../token/TokenStatus";
|
import TokenStatus from "../token/TokenStatus";
|
||||||
import TokenLabel from "../token/TokenLabel";
|
import TokenLabel from "../token/TokenLabel";
|
||||||
@ -43,7 +43,7 @@ function MapToken({
|
|||||||
|
|
||||||
const gridCellPixelSize = useGridCellPixelSize();
|
const gridCellPixelSize = useGridCellPixelSize();
|
||||||
|
|
||||||
const tokenSource = useImageSource(token, tokenSources, unknownSource);
|
const tokenSource = useDataURL(token, tokenSources, unknownSource);
|
||||||
const [tokenSourceImage, tokenSourceStatus] = useImage(tokenSource);
|
const [tokenSourceImage, tokenSourceStatus] = useImage(tokenSource);
|
||||||
const [tokenAspectRatio, setTokenAspectRatio] = useState(1);
|
const [tokenAspectRatio, setTokenAspectRatio] = useState(1);
|
||||||
|
|
||||||
|
@ -3,12 +3,12 @@ import { Box, Image } from "theme-ui";
|
|||||||
|
|
||||||
import usePreventTouch from "../../hooks/usePreventTouch";
|
import usePreventTouch from "../../hooks/usePreventTouch";
|
||||||
|
|
||||||
import { useImageSource } from "../../contexts/ImageSourceContext";
|
import { useDataURL } from "../../contexts/AssetsContext";
|
||||||
|
|
||||||
import { tokenSources, unknownSource } from "../../tokens";
|
import { tokenSources, unknownSource } from "../../tokens";
|
||||||
|
|
||||||
function ListToken({ token, className }) {
|
function ListToken({ token, className }) {
|
||||||
const tokenSource = useImageSource(
|
const tokenURL = useDataURL(
|
||||||
token,
|
token,
|
||||||
tokenSources,
|
tokenSources,
|
||||||
unknownSource,
|
unknownSource,
|
||||||
@ -22,7 +22,7 @@ function ListToken({ token, className }) {
|
|||||||
return (
|
return (
|
||||||
<Box my={2} mx={3} sx={{ width: "48px", height: "48px" }}>
|
<Box my={2} mx={3} sx={{ width: "48px", height: "48px" }}>
|
||||||
<Image
|
<Image
|
||||||
src={tokenSource}
|
src={tokenURL}
|
||||||
ref={imageRef}
|
ref={imageRef}
|
||||||
className={className}
|
className={className}
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -10,7 +10,7 @@ import useImageCenter from "../../hooks/useImageCenter";
|
|||||||
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
|
||||||
|
|
||||||
import { GridProvider } from "../../contexts/GridContext";
|
import { GridProvider } from "../../contexts/GridContext";
|
||||||
import { useImageSource } from "../../contexts/ImageSourceContext";
|
import { useDataURL } from "../../contexts/AssetsContext";
|
||||||
|
|
||||||
import GridOnIcon from "../../icons/GridOnIcon";
|
import GridOnIcon from "../../icons/GridOnIcon";
|
||||||
import GridOffIcon from "../../icons/GridOffIcon";
|
import GridOffIcon from "../../icons/GridOffIcon";
|
||||||
@ -27,12 +27,8 @@ function TokenPreview({ token }) {
|
|||||||
}
|
}
|
||||||
}, [token, tokenSourceData]);
|
}, [token, tokenSourceData]);
|
||||||
|
|
||||||
const tokenSource = useImageSource(
|
const tokenURL = useDataURL(tokenSourceData, tokenSources, unknownSource);
|
||||||
tokenSourceData,
|
const [tokenSourceImage] = useImage(tokenURL);
|
||||||
tokenSources,
|
|
||||||
unknownSource
|
|
||||||
);
|
|
||||||
const [tokenSourceImage] = useImage(tokenSource);
|
|
||||||
|
|
||||||
const [stageWidth, setStageWidth] = useState(1);
|
const [stageWidth, setStageWidth] = useState(1);
|
||||||
const [stageHeight, setStageHeight] = useState(1);
|
const [stageHeight, setStageHeight] = useState(1);
|
||||||
|
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
|
|
||||||
import Tile from "../Tile";
|
import Tile from "../Tile";
|
||||||
|
|
||||||
import { useImageSource } from "../../contexts/ImageSourceContext";
|
import { useAssetURL } from "../../contexts/AssetsContext";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
tokenSources as defaultTokenSources,
|
tokenSources as defaultTokenSources,
|
||||||
@ -18,7 +18,7 @@ function TokenTile({
|
|||||||
canEdit,
|
canEdit,
|
||||||
badges,
|
badges,
|
||||||
}) {
|
}) {
|
||||||
const tokenSource = useImageSource(
|
const tokenURL = useAssetURL(
|
||||||
token,
|
token,
|
||||||
defaultTokenSources,
|
defaultTokenSources,
|
||||||
unknownSource,
|
unknownSource,
|
||||||
@ -27,7 +27,7 @@ function TokenTile({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tile
|
<Tile
|
||||||
src={tokenSource}
|
src={tokenURL}
|
||||||
title={token.name}
|
title={token.name}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onSelect={() => onTokenSelect(token)}
|
onSelect={() => onTokenSelect(token)}
|
||||||
|
273
src/contexts/AssetsContext.js
Normal file
273
src/contexts/AssetsContext.js
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import React, { useState, useContext, useCallback, useEffect } from "react";
|
||||||
|
import { decode } from "@msgpack/msgpack";
|
||||||
|
|
||||||
|
import { useDatabase } from "./DatabaseContext";
|
||||||
|
|
||||||
|
import { omit } from "../helpers/shared";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef Asset
|
||||||
|
* @property {string} id
|
||||||
|
* @property {number} width
|
||||||
|
* @property {number} height
|
||||||
|
* @property {Uint8Array} file
|
||||||
|
* @property {string} mime
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback getAsset
|
||||||
|
* @param {string} assetId
|
||||||
|
* @returns {Promise<Asset|undefined>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef AssetsContext
|
||||||
|
* @property {getAsset} getAsset
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {React.Context<undefined|AssetsContext>}
|
||||||
|
*/
|
||||||
|
const AssetsContext = React.createContext();
|
||||||
|
|
||||||
|
export function AssetsProvider({ children }) {
|
||||||
|
const { worker } = useDatabase();
|
||||||
|
|
||||||
|
const getAsset = useCallback(
|
||||||
|
async (assetId) => {
|
||||||
|
const packed = await worker.loadData("assets", assetId);
|
||||||
|
return decode(packed);
|
||||||
|
},
|
||||||
|
[worker]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AssetsContext.Provider value={{ getAsset }}>
|
||||||
|
{children}
|
||||||
|
</AssetsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAssets() {
|
||||||
|
const context = useContext(AssetsContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useAssets must be used within a AssetsProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef AssetURL
|
||||||
|
* @property {string} url
|
||||||
|
* @property {string} id
|
||||||
|
* @property {number} references
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type React.Context<undefined|Object.<string, AssetURL>>
|
||||||
|
*/
|
||||||
|
export const AssetURLsStateContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type React.Context<undefined|React.Dispatch<React.SetStateAction<{}>>>
|
||||||
|
*/
|
||||||
|
export const AssetURLsUpdaterContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to manage sharing of custom image sources between uses of useAssetURL
|
||||||
|
*/
|
||||||
|
export function AssetURLsProvider({ children }) {
|
||||||
|
const [assetURLs, setAssetURLs] = useState({});
|
||||||
|
|
||||||
|
// Revoke url when no more references
|
||||||
|
useEffect(() => {
|
||||||
|
let urlsToCleanup = [];
|
||||||
|
for (let url of Object.values(assetURLs)) {
|
||||||
|
if (url.references <= 0) {
|
||||||
|
URL.revokeObjectURL(url.url);
|
||||||
|
urlsToCleanup.push(url.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (urlsToCleanup.length > 0) {
|
||||||
|
setAssetURLs((prevURLs) => omit(prevURLs, urlsToCleanup));
|
||||||
|
}
|
||||||
|
}, [assetURLs]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AssetURLsStateContext.Provider value={assetURLs}>
|
||||||
|
<AssetURLsUpdaterContext.Provider value={setAssetURLs}>
|
||||||
|
{children}
|
||||||
|
</AssetURLsUpdaterContext.Provider>
|
||||||
|
</AssetURLsStateContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to load either file or default asset into a URL
|
||||||
|
* @param {string} assetId
|
||||||
|
* @param {"file"|"default"} type
|
||||||
|
* @param {Object.<string, string>} defaultSources
|
||||||
|
* @param {string} unknownSource
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function useAssetURL(assetId, type, defaultSources, unknownSource = "") {
|
||||||
|
const assetURLs = useContext(AssetURLsStateContext);
|
||||||
|
if (assetURLs === undefined) {
|
||||||
|
throw new Error("useAssetURL must be used within a AssetURLsProvider");
|
||||||
|
}
|
||||||
|
const setAssetURLs = useContext(AssetURLsUpdaterContext);
|
||||||
|
if (setAssetURLs === undefined) {
|
||||||
|
throw new Error("useAssetURL must be used within a AssetURLsProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { getAsset } = useAssets();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!assetId || type !== "file") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAssetURL() {
|
||||||
|
const asset = await getAsset(assetId);
|
||||||
|
if (asset) {
|
||||||
|
setAssetURLs((prevURLs) => {
|
||||||
|
if (assetId in prevURLs) {
|
||||||
|
// Check if the asset url is already added
|
||||||
|
return {
|
||||||
|
...prevURLs,
|
||||||
|
[assetId]: {
|
||||||
|
...prevURLs[assetId],
|
||||||
|
// Increase references
|
||||||
|
references: prevURLs[assetId].references + 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(
|
||||||
|
new Blob([asset.file], { type: asset.mime })
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...prevURLs,
|
||||||
|
[assetId]: { url, id: assetId, references: 1 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAssetURL();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Decrease references
|
||||||
|
setAssetURLs((prevURLs) => {
|
||||||
|
if (assetId in prevURLs) {
|
||||||
|
return {
|
||||||
|
...prevURLs,
|
||||||
|
[assetId]: {
|
||||||
|
...prevURLs[assetId],
|
||||||
|
references: prevURLs[assetId].references - 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return prevURLs;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [assetId, setAssetURLs, getAsset, type]);
|
||||||
|
|
||||||
|
if (!assetId) {
|
||||||
|
return unknownSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "default") {
|
||||||
|
return defaultSources[assetId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "file") {
|
||||||
|
return assetURLs[assetId]?.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unknownSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataResolutions = ["ultra", "high", "medium", "low"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef FileData
|
||||||
|
* @property {string} file
|
||||||
|
* @property {"file"} type
|
||||||
|
* @property {string} thumbnail
|
||||||
|
* @property {string=} quality
|
||||||
|
* @property {Object.<string, string>=} resolutions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef DefaultData
|
||||||
|
* @property {string} key
|
||||||
|
* @property {"default"} type
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a map or token into a URL taking into account a thumbnail and multiple resolutions
|
||||||
|
* @param {FileData|DefaultData} data
|
||||||
|
* @param {Object.<string, string>} defaultSources
|
||||||
|
* @param {string} unknownSource
|
||||||
|
* @param {boolean} thumbnail
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function useDataURL(
|
||||||
|
data,
|
||||||
|
defaultSources,
|
||||||
|
unknownSource = "",
|
||||||
|
thumbnail = false
|
||||||
|
) {
|
||||||
|
const { database } = useDatabase();
|
||||||
|
const [assetId, setAssetId] = useState();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
async function loastAssetId() {
|
||||||
|
if (data.type === "default") {
|
||||||
|
setAssetId(data.key);
|
||||||
|
} else {
|
||||||
|
if (thumbnail) {
|
||||||
|
setAssetId(data.thumbnail);
|
||||||
|
} else if (data.resolutions) {
|
||||||
|
const fileKeys = await database
|
||||||
|
.table("assets")
|
||||||
|
.where("id")
|
||||||
|
.equals(data.file)
|
||||||
|
.primaryKeys();
|
||||||
|
const fileExists = fileKeys.length > 0;
|
||||||
|
// Check if a resolution is specified
|
||||||
|
if (data.quality && data.resolutions[data.quality]) {
|
||||||
|
setAssetId(data.resolutions[data.quality]);
|
||||||
|
}
|
||||||
|
// If no file available fallback to the highest resolution
|
||||||
|
else if (!fileExists) {
|
||||||
|
for (let res of dataResolutions) {
|
||||||
|
if (res in data.resolutions) {
|
||||||
|
setAssetId(data.resolutions[res]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setAssetId(data.file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setAssetId(data.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loastAssetId();
|
||||||
|
}, [data, thumbnail, database]);
|
||||||
|
|
||||||
|
const type = data?.type || "default";
|
||||||
|
|
||||||
|
const assetURL = useAssetURL(assetId, type, defaultSources, unknownSource);
|
||||||
|
return assetURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AssetsContext;
|
@ -1,157 +0,0 @@
|
|||||||
import React, { useContext, useState, useEffect } from "react";
|
|
||||||
|
|
||||||
import { omit } from "../helpers/shared";
|
|
||||||
|
|
||||||
export const ImageSourcesStateContext = React.createContext();
|
|
||||||
export const ImageSourcesUpdaterContext = React.createContext(() => {});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to manage sharing of custom image sources between uses of useImageSource
|
|
||||||
*/
|
|
||||||
export function ImageSourcesProvider({ children }) {
|
|
||||||
const [imageSources, setImageSources] = useState({});
|
|
||||||
|
|
||||||
// Revoke url when no more references
|
|
||||||
useEffect(() => {
|
|
||||||
let sourcesToCleanup = [];
|
|
||||||
for (let source of Object.values(imageSources)) {
|
|
||||||
if (source.references <= 0) {
|
|
||||||
URL.revokeObjectURL(source.url);
|
|
||||||
sourcesToCleanup.push(source.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sourcesToCleanup.length > 0) {
|
|
||||||
setImageSources((prevSources) => omit(prevSources, sourcesToCleanup));
|
|
||||||
}
|
|
||||||
}, [imageSources]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ImageSourcesStateContext.Provider value={imageSources}>
|
|
||||||
<ImageSourcesUpdaterContext.Provider value={setImageSources}>
|
|
||||||
{children}
|
|
||||||
</ImageSourcesUpdaterContext.Provider>
|
|
||||||
</ImageSourcesStateContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get id from image data
|
|
||||||
*/
|
|
||||||
function getImageFileId(data, thumbnail) {
|
|
||||||
if (thumbnail) {
|
|
||||||
return `${data.id}-thumbnail`;
|
|
||||||
}
|
|
||||||
if (data.resolutions) {
|
|
||||||
// Check is a resolution is specified
|
|
||||||
if (data.quality && data.resolutions[data.quality]) {
|
|
||||||
return `${data.id}-${data.quality}`;
|
|
||||||
} else if (!data.file) {
|
|
||||||
// Fallback to the highest resolution
|
|
||||||
const resolutionArray = Object.keys(data.resolutions);
|
|
||||||
const resolution = resolutionArray[resolutionArray.length - 1];
|
|
||||||
return `${data.id}-${resolution.id}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to load either file or default image into a URL
|
|
||||||
*/
|
|
||||||
export function useImageSource(data, defaultSources, unknownSource, thumbnail) {
|
|
||||||
const imageSources = useContext(ImageSourcesStateContext);
|
|
||||||
if (imageSources === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
"useImageSource must be used within a ImageSourcesProvider"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const setImageSources = useContext(ImageSourcesUpdaterContext);
|
|
||||||
if (setImageSources === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
"useImageSource must be used within a ImageSourcesProvider"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data || data.type !== "file") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const id = getImageFileId(data, thumbnail);
|
|
||||||
|
|
||||||
function updateImageSource(file) {
|
|
||||||
if (file) {
|
|
||||||
setImageSources((prevSources) => {
|
|
||||||
if (id in prevSources) {
|
|
||||||
// Check if the image source is already added
|
|
||||||
return {
|
|
||||||
...prevSources,
|
|
||||||
[id]: {
|
|
||||||
...prevSources[id],
|
|
||||||
// Increase references
|
|
||||||
references: prevSources[id].references + 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const url = URL.createObjectURL(new Blob([file]));
|
|
||||||
return {
|
|
||||||
...prevSources,
|
|
||||||
[id]: { url, id, references: 1 },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbnail) {
|
|
||||||
updateImageSource(data.thumbnail.file);
|
|
||||||
} else if (data.resolutions) {
|
|
||||||
// Check is a resolution is specified
|
|
||||||
if (data.quality && data.resolutions[data.quality]) {
|
|
||||||
updateImageSource(data.resolutions[data.quality].file);
|
|
||||||
}
|
|
||||||
// If no file available fallback to the highest resolution
|
|
||||||
else if (!data.file) {
|
|
||||||
const resolutionArray = Object.keys(data.resolutions);
|
|
||||||
updateImageSource(
|
|
||||||
data.resolutions[resolutionArray[resolutionArray.length - 1]].file
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
updateImageSource(data.file);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updateImageSource(data.file);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// Decrease references
|
|
||||||
setImageSources((prevSources) => {
|
|
||||||
if (id in prevSources) {
|
|
||||||
return {
|
|
||||||
...prevSources,
|
|
||||||
[id]: {
|
|
||||||
...prevSources[id],
|
|
||||||
references: prevSources[id].references - 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return prevSources;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [data, unknownSource, thumbnail, setImageSources]);
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return unknownSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === "default") {
|
|
||||||
return defaultSources[data.key];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === "file") {
|
|
||||||
const id = getImageFileId(data, thumbnail);
|
|
||||||
return imageSources[id]?.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return unknownSource;
|
|
||||||
}
|
|
133
src/database.js
133
src/database.js
@ -2,6 +2,7 @@
|
|||||||
import Dexie, { Version, DexieOptions } from "dexie";
|
import Dexie, { Version, DexieOptions } from "dexie";
|
||||||
import "dexie-observable";
|
import "dexie-observable";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
import blobToBuffer from "./helpers/blobToBuffer";
|
import blobToBuffer from "./helpers/blobToBuffer";
|
||||||
import { getGridDefaultInset } from "./helpers/grid";
|
import { getGridDefaultInset } from "./helpers/grid";
|
||||||
@ -431,9 +432,139 @@ const versions = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// v1.9.0 - Move map assets into new table
|
||||||
|
23(v) {
|
||||||
|
v.stores({ assets: "id" }).upgrade((tx) => {
|
||||||
|
tx.table("maps").each((map) => {
|
||||||
|
let assets = [];
|
||||||
|
assets.push({
|
||||||
|
id: uuid(),
|
||||||
|
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];
|
||||||
|
assets.push({
|
||||||
|
id: uuid(),
|
||||||
|
file: mapRes.file,
|
||||||
|
width: mapRes.width,
|
||||||
|
height: mapRes.height,
|
||||||
|
mime: "",
|
||||||
|
prevId: map.id,
|
||||||
|
prevType: "mapResolution",
|
||||||
|
resolution,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.push({
|
||||||
|
id: uuid(),
|
||||||
|
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
|
||||||
|
24(v) {
|
||||||
|
v.stores().upgrade((tx) => {
|
||||||
|
tx.table("tokens").each((token) => {
|
||||||
|
let assets = [];
|
||||||
|
assets.push({
|
||||||
|
id: uuid(),
|
||||||
|
file: token.file,
|
||||||
|
width: token.width,
|
||||||
|
height: token.height,
|
||||||
|
mime: "",
|
||||||
|
prevId: token.id,
|
||||||
|
prevType: "token",
|
||||||
|
});
|
||||||
|
assets.push({
|
||||||
|
id: uuid(),
|
||||||
|
file: token.thumbnail.file,
|
||||||
|
width: token.thumbnail.width,
|
||||||
|
height: token.thumbnail.height,
|
||||||
|
mime: "",
|
||||||
|
prevId: token.id,
|
||||||
|
prevType: "tokenThumbnail",
|
||||||
|
});
|
||||||
|
tx.table("assets").bulkAdd(assets);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// v1.9.0 - Create foreign keys for assets
|
||||||
|
25(v) {
|
||||||
|
v.stores().upgrade((tx) => {
|
||||||
|
tx.table("assets").each((asset) => {
|
||||||
|
if (asset.prevType === "map") {
|
||||||
|
tx.table("maps").update(asset.prevId, {
|
||||||
|
file: asset.id,
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
});
|
||||||
|
} else if (asset.prevType === "token") {
|
||||||
|
tx.table("tokens").update(asset.prevId, {
|
||||||
|
file: asset.id,
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
});
|
||||||
|
} 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// v1.9.0 - Remove asset migration helpers
|
||||||
|
26(v) {
|
||||||
|
v.stores().upgrade((tx) => {
|
||||||
|
tx.table("assets")
|
||||||
|
.toCollection()
|
||||||
|
.modify((asset) => {
|
||||||
|
delete asset.prevId;
|
||||||
|
if (asset.prevType === "mapResolution") {
|
||||||
|
delete asset.resolution;
|
||||||
|
}
|
||||||
|
delete asset.prevType;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// v1.9.0 - Remap map resolution assets
|
||||||
|
27(v) {
|
||||||
|
v.stores().upgrade((tx) => {
|
||||||
|
tx.table("maps")
|
||||||
|
.toCollection()
|
||||||
|
.modify((map) => {
|
||||||
|
const resolutions = ["low", "medium", "high", "ultra"];
|
||||||
|
map.resolutions = {};
|
||||||
|
for (let res of resolutions) {
|
||||||
|
if (res in map) {
|
||||||
|
map.resolutions[res] = map[res];
|
||||||
|
delete map[res];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const latestVersion = 22;
|
const latestVersion = 27;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load versions onto a database up to a specific version number
|
* Load versions onto a database up to a specific version number
|
||||||
|
@ -23,10 +23,10 @@ import AuthContext, { useAuth } from "../contexts/AuthContext";
|
|||||||
import SettingsContext, { useSettings } from "../contexts/SettingsContext";
|
import SettingsContext, { useSettings } from "../contexts/SettingsContext";
|
||||||
import KeyboardContext from "../contexts/KeyboardContext";
|
import KeyboardContext from "../contexts/KeyboardContext";
|
||||||
import TokenDataContext, { useTokenData } from "../contexts/TokenDataContext";
|
import TokenDataContext, { useTokenData } from "../contexts/TokenDataContext";
|
||||||
import {
|
import AssetsContext, {
|
||||||
ImageSourcesStateContext,
|
AssetURLsStateContext,
|
||||||
ImageSourcesUpdaterContext,
|
AssetURLsUpdaterContext,
|
||||||
} from "../contexts/ImageSourceContext";
|
} from "../contexts/AssetsContext";
|
||||||
import {
|
import {
|
||||||
useGrid,
|
useGrid,
|
||||||
useGridCellPixelSize,
|
useGridCellPixelSize,
|
||||||
@ -52,8 +52,9 @@ function KonvaBridge({ stageRender, children }) {
|
|||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const tokenData = useTokenData();
|
const tokenData = useTokenData();
|
||||||
const imageSources = useContext(ImageSourcesStateContext);
|
const assets = useContext(AssetsContext);
|
||||||
const setImageSources = useContext(ImageSourcesUpdaterContext);
|
const assetURLs = useContext(AssetURLsStateContext);
|
||||||
|
const setAssetURLs = useContext(AssetURLsUpdaterContext);
|
||||||
const keyboardValue = useContext(KeyboardContext);
|
const keyboardValue = useContext(KeyboardContext);
|
||||||
|
|
||||||
const stageScale = useStageScale();
|
const stageScale = useStageScale();
|
||||||
@ -78,61 +79,63 @@ function KonvaBridge({ stageRender, children }) {
|
|||||||
<SettingsContext.Provider value={settings}>
|
<SettingsContext.Provider value={settings}>
|
||||||
<KeyboardContext.Provider value={keyboardValue}>
|
<KeyboardContext.Provider value={keyboardValue}>
|
||||||
<MapStageProvider value={mapStageRef}>
|
<MapStageProvider value={mapStageRef}>
|
||||||
<TokenDataContext.Provider value={tokenData}>
|
<AssetsContext.Provider value={assets}>
|
||||||
<ImageSourcesStateContext.Provider value={imageSources}>
|
<AssetURLsStateContext.Provider value={assetURLs}>
|
||||||
<ImageSourcesUpdaterContext.Provider value={setImageSources}>
|
<AssetURLsUpdaterContext.Provider value={setAssetURLs}>
|
||||||
<InteractionEmitterContext.Provider
|
<TokenDataContext.Provider value={tokenData}>
|
||||||
value={interactionEmitter}
|
<InteractionEmitterContext.Provider
|
||||||
>
|
value={interactionEmitter}
|
||||||
<SetPreventMapInteractionContext.Provider
|
|
||||||
value={setPreventMapInteraction}
|
|
||||||
>
|
>
|
||||||
<StageWidthContext.Provider value={stageWidth}>
|
<SetPreventMapInteractionContext.Provider
|
||||||
<StageHeightContext.Provider value={stageHeight}>
|
value={setPreventMapInteraction}
|
||||||
<MapWidthContext.Provider value={mapWidth}>
|
>
|
||||||
<MapHeightContext.Provider value={mapHeight}>
|
<StageWidthContext.Provider value={stageWidth}>
|
||||||
<StageScaleContext.Provider value={stageScale}>
|
<StageHeightContext.Provider value={stageHeight}>
|
||||||
<DebouncedStageScaleContext.Provider
|
<MapWidthContext.Provider value={mapWidth}>
|
||||||
value={debouncedStageScale}
|
<MapHeightContext.Provider value={mapHeight}>
|
||||||
>
|
<StageScaleContext.Provider value={stageScale}>
|
||||||
<GridContext.Provider value={grid}>
|
<DebouncedStageScaleContext.Provider
|
||||||
<GridPixelSizeContext.Provider
|
value={debouncedStageScale}
|
||||||
value={gridPixelSize}
|
>
|
||||||
>
|
<GridContext.Provider value={grid}>
|
||||||
<GridCellPixelSizeContext.Provider
|
<GridPixelSizeContext.Provider
|
||||||
value={gridCellPixelSize}
|
value={gridPixelSize}
|
||||||
>
|
>
|
||||||
<GridCellNormalizedSizeContext.Provider
|
<GridCellPixelSizeContext.Provider
|
||||||
value={gridCellNormalizedSize}
|
value={gridCellPixelSize}
|
||||||
>
|
>
|
||||||
<GridOffsetContext.Provider
|
<GridCellNormalizedSizeContext.Provider
|
||||||
value={gridOffset}
|
value={gridCellNormalizedSize}
|
||||||
>
|
>
|
||||||
<GridStrokeWidthContext.Provider
|
<GridOffsetContext.Provider
|
||||||
value={gridStrokeWidth}
|
value={gridOffset}
|
||||||
>
|
>
|
||||||
<GridCellPixelOffsetContext.Provider
|
<GridStrokeWidthContext.Provider
|
||||||
value={gridCellPixelOffset}
|
value={gridStrokeWidth}
|
||||||
>
|
>
|
||||||
{children}
|
<GridCellPixelOffsetContext.Provider
|
||||||
</GridCellPixelOffsetContext.Provider>
|
value={gridCellPixelOffset}
|
||||||
</GridStrokeWidthContext.Provider>
|
>
|
||||||
</GridOffsetContext.Provider>
|
{children}
|
||||||
</GridCellNormalizedSizeContext.Provider>
|
</GridCellPixelOffsetContext.Provider>
|
||||||
</GridCellPixelSizeContext.Provider>
|
</GridStrokeWidthContext.Provider>
|
||||||
</GridPixelSizeContext.Provider>
|
</GridOffsetContext.Provider>
|
||||||
</GridContext.Provider>
|
</GridCellNormalizedSizeContext.Provider>
|
||||||
</DebouncedStageScaleContext.Provider>
|
</GridCellPixelSizeContext.Provider>
|
||||||
</StageScaleContext.Provider>
|
</GridPixelSizeContext.Provider>
|
||||||
</MapHeightContext.Provider>
|
</GridContext.Provider>
|
||||||
</MapWidthContext.Provider>
|
</DebouncedStageScaleContext.Provider>
|
||||||
</StageHeightContext.Provider>
|
</StageScaleContext.Provider>
|
||||||
</StageWidthContext.Provider>
|
</MapHeightContext.Provider>
|
||||||
</SetPreventMapInteractionContext.Provider>
|
</MapWidthContext.Provider>
|
||||||
</InteractionEmitterContext.Provider>
|
</StageHeightContext.Provider>
|
||||||
</ImageSourcesUpdaterContext.Provider>
|
</StageWidthContext.Provider>
|
||||||
</ImageSourcesStateContext.Provider>
|
</SetPreventMapInteractionContext.Provider>
|
||||||
</TokenDataContext.Provider>
|
</InteractionEmitterContext.Provider>
|
||||||
|
</TokenDataContext.Provider>
|
||||||
|
</AssetURLsUpdaterContext.Provider>
|
||||||
|
</AssetURLsStateContext.Provider>
|
||||||
|
</AssetsContext.Provider>
|
||||||
</MapStageProvider>
|
</MapStageProvider>
|
||||||
</KeyboardContext.Provider>
|
</KeyboardContext.Provider>
|
||||||
</SettingsContext.Provider>
|
</SettingsContext.Provider>
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useImage from "use-image";
|
import useImage from "use-image";
|
||||||
|
|
||||||
import { useImageSource } from "../contexts/ImageSourceContext";
|
import { useDataURL } from "../contexts/AssetsContext";
|
||||||
|
|
||||||
import { mapSources as defaultMapSources } from "../maps";
|
import { mapSources as defaultMapSources } from "../maps";
|
||||||
|
|
||||||
function useMapImage(map) {
|
function useMapImage(map) {
|
||||||
const mapSource = useImageSource(map, defaultMapSources);
|
const mapURL = useDataURL(map, defaultMapSources);
|
||||||
const [mapSourceImage, mapSourceImageStatus] = useImage(mapSource);
|
const [mapImage, mapImageStatus] = useImage(mapURL);
|
||||||
|
|
||||||
// Create a map source that only updates when the image is fully loaded
|
// Create a map source that only updates when the image is fully loaded
|
||||||
const [loadedMapSourceImage, setLoadedMapSourceImage] = useState();
|
const [loadedMapImage, setLoadedMapImage] = useState();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mapSourceImageStatus === "loaded") {
|
if (mapImageStatus === "loaded") {
|
||||||
setLoadedMapSourceImage(mapSourceImage);
|
setLoadedMapImage(mapImage);
|
||||||
}
|
}
|
||||||
}, [mapSourceImage, mapSourceImageStatus]);
|
}, [mapImage, mapImageStatus]);
|
||||||
|
|
||||||
return [loadedMapSourceImage, mapSourceImageStatus];
|
return [loadedMapImage, mapImageStatus];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useMapImage;
|
export default useMapImage;
|
||||||
|
@ -15,26 +15,21 @@ let service = {
|
|||||||
* Load either a whole table or individual item from the DB
|
* Load either a whole table or individual item from the DB
|
||||||
* @param {string} table Table to load from
|
* @param {string} table Table to load from
|
||||||
* @param {string=} key Optional database key to load, if undefined whole table will be loaded
|
* @param {string=} key Optional database key to load, if undefined whole table will be loaded
|
||||||
* @param {bool} excludeFiles Optional exclude files from loaded data when using whole table loading
|
|
||||||
*/
|
*/
|
||||||
async loadData(table, key, excludeFiles = true) {
|
async loadData(table, key) {
|
||||||
try {
|
try {
|
||||||
let db = getDatabase({});
|
let db = getDatabase({});
|
||||||
if (key) {
|
if (key) {
|
||||||
// Load specific item
|
// Load specific item
|
||||||
const data = await db.table(table).get(key);
|
const data = await db.table(table).get(key);
|
||||||
return data;
|
const packed = encode(data);
|
||||||
|
return Comlink.transfer(packed, [packed.buffer]);
|
||||||
} else {
|
} else {
|
||||||
// Load entire table
|
// Load entire table
|
||||||
let items = [];
|
let items = [];
|
||||||
// Use a cursor instead of toArray to prevent IPC max size error
|
// Use a cursor instead of toArray to prevent IPC max size error
|
||||||
await db.table(table).each((item) => {
|
await db.table(table).each((item) => {
|
||||||
if (excludeFiles) {
|
items.push(item);
|
||||||
const { file, resolutions, ...rest } = item;
|
|
||||||
items.push(rest);
|
|
||||||
} else {
|
|
||||||
items.push(item);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pack data with msgpack so we can use transfer to avoid memory issues
|
// Pack data with msgpack so we can use transfer to avoid memory issues
|
||||||
|
@ -13070,6 +13070,11 @@ uuid@^8.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
|
||||||
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
|
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
|
||||||
|
|
||||||
|
uuid@^8.3.2:
|
||||||
|
version "8.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3:
|
v8-compile-cache@^2.0.3:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
|
||||||
|
Loading…
Reference in New Issue
Block a user