226 lines
6.3 KiB
JavaScript
226 lines
6.3 KiB
JavaScript
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 (
|
|
<Flex sx={{ height: "100%", flexGrow: 1 }} {...containerListeners}>
|
|
{children}
|
|
{dragging && (
|
|
<Flex
|
|
sx={{
|
|
position: "absolute",
|
|
top: 0,
|
|
right: 0,
|
|
left: 0,
|
|
bottom: 0,
|
|
cursor: "copy",
|
|
flexDirection: "column",
|
|
}}
|
|
{...overlayListeners}
|
|
>
|
|
<Flex
|
|
bg="overlay"
|
|
sx={{
|
|
height: "10%",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
color: droppingType === "maps" ? "primary" : "text",
|
|
opacity: droppingType === "maps" ? 1 : 0.8,
|
|
margin: "4px 16px",
|
|
border: "1px dashed",
|
|
width: "calc(100% - 32px)",
|
|
borderRadius: "12px",
|
|
}}
|
|
onDragEnter={handleMapsOver}
|
|
>
|
|
<Text sx={{ pointerEvents: "none", userSelect: "none" }}>
|
|
Drop as map
|
|
</Text>
|
|
</Flex>
|
|
<Flex
|
|
bg="overlay"
|
|
sx={{
|
|
flexGrow: 1,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
color: droppingType === "tokens" ? "primary" : "text",
|
|
opacity: droppingType === "tokens" ? 1 : 0.8,
|
|
margin: "4px 16px",
|
|
border: "1px dashed",
|
|
width: "calc(100% - 32px)",
|
|
borderRadius: "12px",
|
|
}}
|
|
onDragEnter={handleTokensOver}
|
|
>
|
|
<Text sx={{ pointerEvents: "none", userSelect: "none" }}>
|
|
Drop as token
|
|
</Text>
|
|
</Flex>
|
|
</Flex>
|
|
)}
|
|
<ConfirmModal
|
|
isOpen={isLargeImageWarningModalOpen}
|
|
onRequestClose={handleLargeImageWarningCancel}
|
|
onConfirm={handleLargeImageWarningConfirm}
|
|
confirmText="Continue"
|
|
label="Warning"
|
|
description="An imported image is larger than 20MB, this may cause slowness. Continue?"
|
|
/>
|
|
{isLoading && <LoadingOverlay bg="overlay" />}
|
|
</Flex>
|
|
);
|
|
}
|
|
|
|
export default GlobalImageDrop;
|