Add a global image drop for dropping directly to the map screen

This commit is contained in:
Mitchell McCaffrey 2021-06-06 21:04:46 +10:00
parent 4e7ed1d625
commit 98f7744c1f
7 changed files with 178 additions and 7 deletions

View File

@ -44,6 +44,18 @@ function StyledModal({
{content}
</animated.div>
)}
overlayElement={(props, content) => (
<div
onDragEnter={(e) => {
// Prevent drag event from triggering with a modal open
e.preventDefault();
e.stopPropagation();
}}
{...props}
>
{content}
</div>
)}
{...props}
>
{children}

View File

@ -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 (
<Box sx={{ height: "100%" }}>
<ImageDrop onDrop={handleDrop}>{children}</ImageDrop>
<ImageTypeModal
isOpen={isImageTypeModalOpen}
onMaps={handleMaps}
onTokens={handleTokens}
onRequestClose={handleImageTypeClose}
multiple={droppedImagesRef.current?.length > 1}
/>
<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" />}
</Box>
);
}
export default GlobalImageDrop;

View File

@ -68,7 +68,7 @@ function ImageDrop({ onDrop, dropText, children }) {
}
return (
<Box onDragEnter={handleImageDragEnter}>
<Box onDragEnter={handleImageDragEnter} sx={{ height: "100%" }}>
{children}
{dragging && (
<Flex

View File

@ -0,0 +1,34 @@
import React from "react";
import { Box, Label, Flex, Button } from "theme-ui";
import Modal from "../components/Modal";
function ImageTypeModal({
isOpen,
onRequestClose,
multiple,
onTokens,
onMaps,
}) {
return (
<Modal
isOpen={isOpen}
onRequestClose={onRequestClose}
style={{ maxWidth: "300px" }}
>
<Box>
<Label py={2}>Import image{multiple ? "s" : ""} as</Label>
<Flex py={2}>
<Button sx={{ flexGrow: 1 }} m={1} ml={0} onClick={onTokens}>
Token{multiple ? "s" : ""}
</Button>
<Button sx={{ flexGrow: 1 }} m={1} mr={0} onClick={onMaps}>
Map{multiple ? "s" : ""}
</Button>
</Flex>
</Box>
</Modal>
);
}
export default ImageTypeModal;

View File

@ -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)" }}
>
<ImageDrop onDrop={handleImagesUpload} dropText="Drop map to upload">
<ImageDrop onDrop={handleImagesUpload} dropText="Drop map to import">
<input
onChange={(event) => handleImagesUpload(event.target.files)}
type="file"

View File

@ -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)" }}
>
<ImageDrop onDrop={handleImagesUpload} dropText="Drop token to upload">
<ImageDrop onDrop={handleImagesUpload} dropText="Drop token to import">
<input
onChange={(event) => handleImagesUpload(event.target.files)}
type="file"

View File

@ -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() {
<PlayerProvider session={session}>
<PartyProvider session={session}>
<MapStageProvider value={mapStageRef}>
<Flex sx={{ flexDirection: "column", height: "100%" }}>
<GlobalImageDrop>
<Flex
sx={{
justifyContent: "space-between",
@ -125,7 +126,7 @@ function Game() {
<NetworkedParty session={session} gameId={gameId} />
<NetworkedMapAndTokens session={session} />
</Flex>
</Flex>
</GlobalImageDrop>
<Banner
isOpen={!!peerError}
onRequestClose={() => setPeerError(null)}