Add a global image drop for dropping directly to the map screen
This commit is contained in:
parent
4e7ed1d625
commit
98f7744c1f
@ -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}
|
||||
|
122
src/components/file/GlobalImageDrop.js
Normal file
122
src/components/file/GlobalImageDrop.js
Normal 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;
|
@ -68,7 +68,7 @@ function ImageDrop({ onDrop, dropText, children }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box onDragEnter={handleImageDragEnter}>
|
||||
<Box onDragEnter={handleImageDragEnter} sx={{ height: "100%" }}>
|
||||
{children}
|
||||
{dragging && (
|
||||
<Flex
|
34
src/modals/ImageTypeModal.js
Normal file
34
src/modals/ImageTypeModal.js
Normal 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;
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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)}
|
||||
|
Loading…
Reference in New Issue
Block a user