From a023ef61ed77f9219dcd1c9fcc807993a6e46d7d Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Fri, 23 Apr 2021 11:48:24 +1000 Subject: [PATCH] Fix token and map editing and viewing --- src/contexts/AssetsContext.js | 38 +++++++--- src/database.js | 4 -- src/helpers/KonvaBridge.js | 123 +++++++++++++++++--------------- src/helpers/image.js | 16 +++-- src/modals/SelectMapModal.js | 41 ++++++++--- src/modals/SelectTokensModal.js | 31 ++++++-- 6 files changed, 155 insertions(+), 98 deletions(-) diff --git a/src/contexts/AssetsContext.js b/src/contexts/AssetsContext.js index 77b40c2..98bbb2d 100644 --- a/src/contexts/AssetsContext.js +++ b/src/contexts/AssetsContext.js @@ -20,9 +20,15 @@ import { omit } from "../helpers/shared"; * @returns {Promise} */ +/** + * @callback addAssets + * @param {Asset[]} assets + */ + /** * @typedef AssetsContext * @property {getAsset} getAsset + * @property {addAssets} addAssets */ /** @@ -31,7 +37,7 @@ import { omit } from "../helpers/shared"; const AssetsContext = React.createContext(); export function AssetsProvider({ children }) { - const { worker } = useDatabase(); + const { worker, database } = useDatabase(); const getAsset = useCallback( async (assetId) => { @@ -41,10 +47,20 @@ export function AssetsProvider({ children }) { [worker] ); + const addAssets = useCallback( + async (assets) => { + return database.table("assets").bulkAdd(assets); + }, + [database] + ); + + const value = { + getAsset, + addAssets, + }; + return ( - - {children} - + {children} ); } @@ -107,10 +123,10 @@ export function AssetURLsProvider({ children }) { * @param {string} assetId * @param {"file"|"default"} type * @param {Object.} defaultSources - * @param {string} unknownSource - * @returns {string} + * @param {string|undefined} unknownSource + * @returns {string|undefined} */ -export function useAssetURL(assetId, type, defaultSources, unknownSource = "") { +export function useAssetURL(assetId, type, defaultSources, unknownSource) { const assetURLs = useContext(AssetURLsStateContext); if (assetURLs === undefined) { throw new Error("useAssetURL must be used within a AssetURLsProvider"); @@ -183,7 +199,7 @@ export function useAssetURL(assetId, type, defaultSources, unknownSource = "") { } if (type === "file") { - return assetURLs[assetId]?.url; + return assetURLs[assetId]?.url || unknownSource; } return unknownSource; @@ -210,14 +226,14 @@ const dataResolutions = ["ultra", "high", "medium", "low"]; * Load a map or token into a URL taking into account a thumbnail and multiple resolutions * @param {FileData|DefaultData} data * @param {Object.} defaultSources - * @param {string} unknownSource + * @param {string|undefined} unknownSource * @param {boolean} thumbnail - * @returns {string} + * @returns {string|undefined} */ export function useDataURL( data, defaultSources, - unknownSource = "", + unknownSource, thumbnail = false ) { const { database } = useDatabase(); diff --git a/src/database.js b/src/database.js index 3257635..af20c1d 100644 --- a/src/database.js +++ b/src/database.js @@ -509,14 +509,10 @@ const versions = { 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 }); diff --git a/src/helpers/KonvaBridge.js b/src/helpers/KonvaBridge.js index 0573859..6aeecb5 100644 --- a/src/helpers/KonvaBridge.js +++ b/src/helpers/KonvaBridge.js @@ -43,6 +43,7 @@ import { GridStrokeWidthContext, GridCellPixelOffsetContext, } from "../contexts/GridContext"; +import DatabaseContext, { useDatabase } from "../contexts/DatabaseContext"; /** * Provide a bridge for konva that forwards our contexts @@ -74,72 +75,78 @@ function KonvaBridge({ stageRender, children }) { const gridCellPixelOffset = useGridCellPixelOffset(); const gridOffset = useGridOffset(); + const database = useDatabase(); + return stageRender( - - - - - - - - - - + + + + + + + + + - - - - - - + + + + + - - - + + - - - - - {children} - - - - - - - - - - - - - - - - - - - - - - - + + {children} + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/helpers/image.js b/src/helpers/image.js index fbcf7a8..fb33712 100644 --- a/src/helpers/image.js +++ b/src/helpers/image.js @@ -1,3 +1,5 @@ +import { v4 as uuid } from "uuid"; + import blobToBuffer from "./blobToBuffer"; const lightnessDetectionOffset = 0.1; @@ -88,12 +90,12 @@ export async function resizeImage(image, size, type, quality) { } /** - * @typedef ImageFile - * @property {Uint8Array|null} file + * @typedef Asset + * @property {string} id * @property {number} width * @property {number} height - * @property {"file"} type - * @property {string} id + * @property {Uint8Array} file + * @property {string} mime */ /** @@ -102,7 +104,7 @@ export async function resizeImage(image, size, type, quality) { * @param {string} type the mime type of the image * @param {number} size the width and height of the thumbnail * @param {number} quality if image is a jpeg or webp this is the quality setting - * @returns {Promise} + * @returns {Promise} */ export async function createThumbnail(image, type, size = 300, quality = 0.5) { let canvas = document.createElement("canvas"); @@ -150,7 +152,7 @@ export async function createThumbnail(image, type, size = 300, quality = 0.5) { file: thumbnailBuffer, width: thumbnailImage.width, height: thumbnailImage.height, - type: "file", - id: "thumbnail", + mime: type, + id: uuid(), }; } diff --git a/src/modals/SelectMapModal.js b/src/modals/SelectMapModal.js index 517bc45..f759ac3 100644 --- a/src/modals/SelectMapModal.js +++ b/src/modals/SelectMapModal.js @@ -1,6 +1,6 @@ import React, { useRef, useState } from "react"; import { Button, Flex, Label } from "theme-ui"; -import shortid from "shortid"; +import { v4 as uuid } from "uuid"; import Case from "case"; import { useToasts } from "react-toast-notifications"; @@ -28,6 +28,7 @@ import useResponsiveLayout from "../hooks/useResponsiveLayout"; import { useMapData } from "../contexts/MapDataContext"; import { useAuth } from "../contexts/AuthContext"; import { useKeyboard, useBlur } from "../contexts/KeyboardContext"; +import { useAssets } from "../contexts/AssetsContext"; import shortcuts from "../shortcuts"; @@ -72,6 +73,7 @@ function SelectMapModal({ getMapFromDB, getMapStateFromDB, } = useMapData(); + const { addAssets } = useAssets(); /** * Search @@ -221,6 +223,8 @@ function SelectMapModal({ gridSize = { x: 22, y: 22 }; } + let assets = []; + // Create resolutions const resolutions = {}; for (let resolution of mapResolutions) { @@ -239,26 +243,38 @@ function SelectMapModal({ resolution.quality ); if (resized.blob) { + const assetId = uuid(); + resolutions[resolution.id] = assetId; const resizedBuffer = await blobToBuffer(resized.blob); - resolutions[resolution.id] = { + const asset = { file: resizedBuffer, width: resized.width, height: resized.height, - type: "file", - id: resolution.id, + id: assetId, + mime: file.type, }; + assets.push(asset); } } } // Create thumbnail const thumbnail = await createThumbnail(image, file.type); + assets.push(thumbnail); - handleMapAdd({ - // Save as a buffer to send with msgpack + const fileAsset = { + id: uuid(), file: buffer, - resolutions, - thumbnail, + width: image.width, + height: image.height, + mime: file.type, + }; + assets.push(fileAsset); + + const map = { name, + resolutions, + file: fileAsset.id, + thumbnail: thumbnail.id, type: "file", grid: { size: gridSize, @@ -275,13 +291,15 @@ function SelectMapModal({ }, width: image.width, height: image.height, - id: shortid.generate(), + id: uuid(), created: Date.now(), lastModified: Date.now(), lastUsed: Date.now(), owner: userId, ...defaultMapProps, - }); + }; + + handleMapAdd(map, assets); setIsLoading(false); URL.revokeObjectURL(url); resolve(); @@ -311,8 +329,9 @@ function SelectMapModal({ selectedMapIds.includes(state.mapId) ); - async function handleMapAdd(map) { + async function handleMapAdd(map, assets) { await addMap(map); + await addAssets(assets); setSelectedMapIds([map.id]); } diff --git a/src/modals/SelectTokensModal.js b/src/modals/SelectTokensModal.js index 91789c5..6f04bb9 100644 --- a/src/modals/SelectTokensModal.js +++ b/src/modals/SelectTokensModal.js @@ -1,6 +1,6 @@ import React, { useRef, useState } from "react"; import { Flex, Label, Button } from "theme-ui"; -import shortid from "shortid"; +import { v4 as uuid } from "uuid"; import Case from "case"; import { useToasts } from "react-toast-notifications"; @@ -22,6 +22,7 @@ import useResponsiveLayout from "../hooks/useResponsiveLayout"; import { useTokenData } from "../contexts/TokenDataContext"; import { useAuth } from "../contexts/AuthContext"; import { useKeyboard, useBlur } from "../contexts/KeyboardContext"; +import { useAssets } from "../contexts/AssetsContext"; import shortcuts from "../shortcuts"; @@ -36,6 +37,7 @@ function SelectTokensModal({ isOpen, onRequestClose }) { updateTokens, tokensLoading, } = useTokenData(); + const { addAssets } = useAssets(); /** * Search @@ -160,13 +162,24 @@ function SelectTokensModal({ isOpen, onRequestClose }) { return new Promise((resolve, reject) => { image.onload = async function () { + let assets = []; const thumbnail = await createThumbnail(image, file.type); + assets.push(thumbnail); - handleTokenAdd({ + const fileAsset = { + id: uuid(), file: buffer, - thumbnail, + width: image.width, + height: image.height, + mime: file.type, + }; + assets.push(fileAsset); + + const token = { name, - id: shortid.generate(), + thumbnail: thumbnail.id, + file: fileAsset.id, + id: uuid(), type: "file", created: Date.now(), lastModified: Date.now(), @@ -178,8 +191,11 @@ function SelectTokensModal({ isOpen, onRequestClose }) { group: "", width: image.width, height: image.height, - }); + }; + + handleTokenAdd(token, assets); setIsLoading(false); + URL.revokeObjectURL(url); resolve(); }; image.onerror = reject; @@ -196,8 +212,9 @@ function SelectTokensModal({ isOpen, onRequestClose }) { selectedTokenIds.includes(token.id) ); - function handleTokenAdd(token) { - addToken(token); + async function handleTokenAdd(token, assets) { + await addToken(token); + await addAssets(assets); setSelectedTokenIds([token.id]); }