import React, { useState, useRef } from "react"; import { Flex, Text } from "theme-ui"; import { useToasts } from "react-toast-notifications"; import LoadingOverlay from "../LoadingOverlay"; import ConfirmModal from "../../modals/ConfirmModal"; import { createMapFromFile } from "../../helpers/map"; import { createTokenFromFile } from "../../helpers/token"; import { createTokenState, clientPositionToMapPosition, } from "../../helpers/token"; import Vector2 from "../../helpers/Vector2"; import { useAuth } from "../../contexts/AuthContext"; import { useMapData } from "../../contexts/MapDataContext"; import { useTokenData } from "../../contexts/TokenDataContext"; import { useAssets } from "../../contexts/AssetsContext"; import { useMapStage } from "../../contexts/MapStageContext"; import useImageDrop from "../../hooks/useImageDrop"; function GlobalImageDrop({ children, onMapChange, onMapTokensStateCreate }) { const { addToast } = useToasts(); const { userId } = useAuth(); const { addMap, getMapState } = useMapData(); const { addToken } = useTokenData(); const { addAssets } = useAssets(); const mapStageRef = useMapStage(); const [isLargeImageWarningModalOpen, setShowLargeImageWarning] = useState( false ); const [isLoading, setIsLoading] = useState(false); const droppedImagesRef = useRef(); const dropPositionRef = useRef(); // maps or tokens const [droppingType, setDroppingType] = useState("maps"); async function handleDrop(files, dropPosition) { if (navigator.storage) { // Attempt to enable persistant storage await navigator.storage.persist(); } dropPositionRef.current = dropPosition; droppedImagesRef.current = []; for (let file of files) { if (file.size > 5e7) { addToast(`Unable to import image ${file.name} as it is over 50MB`); } else { droppedImagesRef.current.push(file); } } // Any file greater than 20MB if (droppedImagesRef.current.some((file) => file.size > 2e7)) { setShowLargeImageWarning(true); return; } if (droppingType === "maps") { await handleMaps(); } else { await handleTokens(); } } function handleLargeImageWarningCancel() { droppedImagesRef.current = undefined; setShowLargeImageWarning(false); } async function handleLargeImageWarningConfirm() { setShowLargeImageWarning(false); if (droppingType === "maps") { await handleMaps(); } else { await handleTokens(); } } async function handleMaps() { setIsLoading(true); let maps = []; for (let file of droppedImagesRef.current) { const { map, assets } = await createMapFromFile(file, userId); await addMap(map); await addAssets(assets); maps.push(map); } // Change map if only 1 dropped if (maps.length === 1) { const mapState = await getMapState(maps[0].id); onMapChange(maps[0], mapState); } setIsLoading(false); droppedImagesRef.current = undefined; } async function handleTokens() { setIsLoading(true); // Keep track of tokens so we can add them to the map let tokens = []; for (let file of droppedImagesRef.current) { const { token, assets } = await createTokenFromFile(file, userId); await addToken(token); await addAssets(assets); tokens.push(token); } setIsLoading(false); droppedImagesRef.current = undefined; const dropPosition = dropPositionRef.current; const mapStage = mapStageRef.current; if (mapStage && dropPosition) { const mapPosition = clientPositionToMapPosition(mapStage, dropPosition); if (mapPosition) { let tokenStates = []; let offset = new Vector2(0, 0); for (let token of tokens) { if (token) { tokenStates.push( createTokenState(token, Vector2.add(mapPosition, offset), userId) ); offset = Vector2.add(offset, 0.01); } } if (tokenStates.length > 0) { onMapTokensStateCreate(tokenStates); } } } } function handleMapsOver() { setDroppingType("maps"); } function handleTokensOver() { setDroppingType("tokens"); } const { dragging, containerListeners, overlayListeners } = useImageDrop( handleDrop ); return ( {children} {dragging && ( Drop as map Drop as token )} {isLoading && } ); } export default GlobalImageDrop;