diff --git a/src/components/Modal.js b/src/components/Modal.js
index ae2fdcf..43db923 100644
--- a/src/components/Modal.js
+++ b/src/components/Modal.js
@@ -44,6 +44,18 @@ function StyledModal({
{content}
)}
+ overlayElement={(props, content) => (
+
{
+ // Prevent drag event from triggering with a modal open
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ {...props}
+ >
+ {content}
+
+ )}
{...props}
>
{children}
diff --git a/src/components/file/GlobalImageDrop.js b/src/components/file/GlobalImageDrop.js
new file mode 100644
index 0000000..a03578b
--- /dev/null
+++ b/src/components/file/GlobalImageDrop.js
@@ -0,0 +1,122 @@
+import React, { useState, useRef } from "react";
+import { Box } from "theme-ui";
+import { useToasts } from "react-toast-notifications";
+
+import ImageDrop from "./ImageDrop";
+
+import LoadingOverlay from "../LoadingOverlay";
+
+import ImageTypeModal from "../../modals/ImageTypeModal";
+import ConfirmModal from "../../modals/ConfirmModal";
+
+import { createMapFromFile } from "../../helpers/map";
+import { createTokenFromFile } from "../../helpers/token";
+
+import { useAuth } from "../../contexts/AuthContext";
+import { useMapData } from "../../contexts/MapDataContext";
+import { useTokenData } from "../../contexts/TokenDataContext";
+import { useAssets } from "../../contexts/AssetsContext";
+
+function GlobalImageDrop({ children }) {
+ const { addToast } = useToasts();
+
+ const { userId } = useAuth();
+ const { addMap } = useMapData();
+ const { addToken } = useTokenData();
+ const { addAssets } = useAssets();
+
+ const [isImageTypeModalOpen, setIsImageTypeModalOpen] = useState(false);
+ const [isLargeImageWarningModalOpen, setShowLargeImageWarning] = useState(
+ false
+ );
+ const [isLoading, setIsLoading] = useState(false);
+
+ const droppedImagesRef = useRef();
+
+ async function handleDrop(files) {
+ if (navigator.storage) {
+ // Attempt to enable persistant storage
+ await navigator.storage.persist();
+ }
+
+ 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;
+ }
+
+ setIsImageTypeModalOpen(true);
+ }
+
+ function handleLargeImageWarningCancel() {
+ droppedImagesRef.current = undefined;
+ setShowLargeImageWarning(false);
+ }
+
+ async function handleLargeImageWarningConfirm() {
+ setShowLargeImageWarning(false);
+ setIsImageTypeModalOpen(true);
+ }
+
+ async function handleMaps() {
+ setIsImageTypeModalOpen(false);
+ setIsLoading(true);
+ for (let file of droppedImagesRef.current) {
+ const { map, assets } = await createMapFromFile(file, userId);
+ await addMap(map);
+ await addAssets(assets);
+ }
+ setIsLoading(false);
+ droppedImagesRef.current = undefined;
+ }
+
+ async function handleTokens() {
+ setIsImageTypeModalOpen(false);
+ setIsLoading(true);
+ for (let file of droppedImagesRef.current) {
+ const { token, assets } = await createTokenFromFile(file, userId);
+ await addToken(token);
+ await addAssets(assets);
+ }
+ setIsLoading(false);
+ droppedImagesRef.current = undefined;
+ }
+
+ function handleImageTypeClose() {
+ droppedImagesRef.current = undefined;
+ setIsImageTypeModalOpen(false);
+ }
+
+ return (
+
+ {children}
+ 1}
+ />
+
+ {isLoading && }
+
+ );
+}
+
+export default GlobalImageDrop;
diff --git a/src/components/ImageDrop.js b/src/components/file/ImageDrop.js
similarity index 97%
rename from src/components/ImageDrop.js
rename to src/components/file/ImageDrop.js
index 7ee9c74..85bf8af 100644
--- a/src/components/ImageDrop.js
+++ b/src/components/file/ImageDrop.js
@@ -68,7 +68,7 @@ function ImageDrop({ onDrop, dropText, children }) {
}
return (
-
+
{children}
{dragging && (
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ImageTypeModal;
diff --git a/src/modals/SelectMapModal.js b/src/modals/SelectMapModal.js
index 468444a..32a6532 100644
--- a/src/modals/SelectMapModal.js
+++ b/src/modals/SelectMapModal.js
@@ -7,9 +7,10 @@ import EditMapModal from "./EditMapModal";
import ConfirmModal from "./ConfirmModal";
import Modal from "../components/Modal";
-import ImageDrop from "../components/ImageDrop";
import LoadingOverlay from "../components/LoadingOverlay";
+import ImageDrop from "../components/file/ImageDrop";
+
import MapTiles from "../components/map/MapTiles";
import MapEditBar from "../components/map/MapEditBar";
import SelectMapSelectButton from "../components/map/SelectMapSelectButton";
@@ -198,7 +199,7 @@ function SelectMapModal({
onRequestClose={handleClose}
style={{ maxWidth: layout.modalSize, width: "calc(100% - 16px)" }}
>
-
+
handleImagesUpload(event.target.files)}
type="file"
diff --git a/src/modals/SelectTokensModal.js b/src/modals/SelectTokensModal.js
index df27357..fb0a90b 100644
--- a/src/modals/SelectTokensModal.js
+++ b/src/modals/SelectTokensModal.js
@@ -7,9 +7,10 @@ import EditTokenModal from "./EditTokenModal";
import ConfirmModal from "./ConfirmModal";
import Modal from "../components/Modal";
-import ImageDrop from "../components/ImageDrop";
import LoadingOverlay from "../components/LoadingOverlay";
+import ImageDrop from "../components/file/ImageDrop";
+
import TokenTiles from "../components/token/TokenTiles";
import TokenEditBar from "../components/token/TokenEditBar";
@@ -199,7 +200,7 @@ function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) {
onRequestClose={onRequestClose}
style={{ maxWidth: layout.modalSize, width: "calc(100% - 16px)" }}
>
-
+
handleImagesUpload(event.target.files)}
type="file"
diff --git a/src/routes/Game.js b/src/routes/Game.js
index aa324c2..113ffae 100644
--- a/src/routes/Game.js
+++ b/src/routes/Game.js
@@ -8,6 +8,7 @@ import OfflineBanner from "../components/banner/OfflineBanner";
import LoadingOverlay from "../components/LoadingOverlay";
import Link from "../components/Link";
import MapLoadingOverlay from "../components/map/MapLoadingOverlay";
+import GlobalImageDrop from "../components/file/GlobalImageDrop";
import AuthModal from "../modals/AuthModal";
import GameExpiredModal from "../modals/GameExpiredModal";
@@ -114,7 +115,7 @@ function Game() {
-
+
-
+
setPeerError(null)}