Refactor image drop and to a hook and move global image drop
This commit is contained in:
parent
98f7744c1f
commit
eb9afcc66a
@ -1,12 +1,9 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState, useRef } from "react";
|
||||||
import { Box } from "theme-ui";
|
import { Flex, Text } from "theme-ui";
|
||||||
import { useToasts } from "react-toast-notifications";
|
import { useToasts } from "react-toast-notifications";
|
||||||
|
|
||||||
import ImageDrop from "./ImageDrop";
|
|
||||||
|
|
||||||
import LoadingOverlay from "../LoadingOverlay";
|
import LoadingOverlay from "../LoadingOverlay";
|
||||||
|
|
||||||
import ImageTypeModal from "../../modals/ImageTypeModal";
|
|
||||||
import ConfirmModal from "../../modals/ConfirmModal";
|
import ConfirmModal from "../../modals/ConfirmModal";
|
||||||
|
|
||||||
import { createMapFromFile } from "../../helpers/map";
|
import { createMapFromFile } from "../../helpers/map";
|
||||||
@ -17,7 +14,9 @@ import { useMapData } from "../../contexts/MapDataContext";
|
|||||||
import { useTokenData } from "../../contexts/TokenDataContext";
|
import { useTokenData } from "../../contexts/TokenDataContext";
|
||||||
import { useAssets } from "../../contexts/AssetsContext";
|
import { useAssets } from "../../contexts/AssetsContext";
|
||||||
|
|
||||||
function GlobalImageDrop({ children }) {
|
import useImageDrop from "../../hooks/useImageDrop";
|
||||||
|
|
||||||
|
function GlobalImageDrop({ children, onMapTokensStateCreate }) {
|
||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
|
|
||||||
const { userId } = useAuth();
|
const { userId } = useAuth();
|
||||||
@ -25,20 +24,24 @@ function GlobalImageDrop({ children }) {
|
|||||||
const { addToken } = useTokenData();
|
const { addToken } = useTokenData();
|
||||||
const { addAssets } = useAssets();
|
const { addAssets } = useAssets();
|
||||||
|
|
||||||
const [isImageTypeModalOpen, setIsImageTypeModalOpen] = useState(false);
|
|
||||||
const [isLargeImageWarningModalOpen, setShowLargeImageWarning] = useState(
|
const [isLargeImageWarningModalOpen, setShowLargeImageWarning] = useState(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const droppedImagesRef = useRef();
|
const droppedImagesRef = useRef();
|
||||||
|
const dropPositionRef = useRef();
|
||||||
|
// maps or tokens
|
||||||
|
const [droppingType, setDroppingType] = useState("maps");
|
||||||
|
|
||||||
async function handleDrop(files) {
|
async function handleDrop(files, dropPosition) {
|
||||||
if (navigator.storage) {
|
if (navigator.storage) {
|
||||||
// Attempt to enable persistant storage
|
// Attempt to enable persistant storage
|
||||||
await navigator.storage.persist();
|
await navigator.storage.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dropPositionRef.current = dropPosition;
|
||||||
|
|
||||||
droppedImagesRef.current = [];
|
droppedImagesRef.current = [];
|
||||||
for (let file of files) {
|
for (let file of files) {
|
||||||
if (file.size > 5e7) {
|
if (file.size > 5e7) {
|
||||||
@ -54,7 +57,11 @@ function GlobalImageDrop({ children }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsImageTypeModalOpen(true);
|
if (droppingType === "maps") {
|
||||||
|
await handleMaps();
|
||||||
|
} else {
|
||||||
|
await handleTokens();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLargeImageWarningCancel() {
|
function handleLargeImageWarningCancel() {
|
||||||
@ -64,11 +71,14 @@ function GlobalImageDrop({ children }) {
|
|||||||
|
|
||||||
async function handleLargeImageWarningConfirm() {
|
async function handleLargeImageWarningConfirm() {
|
||||||
setShowLargeImageWarning(false);
|
setShowLargeImageWarning(false);
|
||||||
setIsImageTypeModalOpen(true);
|
if (droppingType === "maps") {
|
||||||
|
await handleMaps();
|
||||||
|
} else {
|
||||||
|
await handleTokens();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMaps() {
|
async function handleMaps() {
|
||||||
setIsImageTypeModalOpen(false);
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
for (let file of droppedImagesRef.current) {
|
for (let file of droppedImagesRef.current) {
|
||||||
const { map, assets } = await createMapFromFile(file, userId);
|
const { map, assets } = await createMapFromFile(file, userId);
|
||||||
@ -80,7 +90,6 @@ function GlobalImageDrop({ children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleTokens() {
|
async function handleTokens() {
|
||||||
setIsImageTypeModalOpen(false);
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
for (let file of droppedImagesRef.current) {
|
for (let file of droppedImagesRef.current) {
|
||||||
const { token, assets } = await createTokenFromFile(file, userId);
|
const { token, assets } = await createTokenFromFile(file, userId);
|
||||||
@ -91,21 +100,66 @@ function GlobalImageDrop({ children }) {
|
|||||||
droppedImagesRef.current = undefined;
|
droppedImagesRef.current = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleImageTypeClose() {
|
function handleMapsOver() {
|
||||||
droppedImagesRef.current = undefined;
|
setDroppingType("maps");
|
||||||
setIsImageTypeModalOpen(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleTokensOver() {
|
||||||
|
setDroppingType("tokens");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { dragging, containerListeners, overlayListeners } = useImageDrop(
|
||||||
|
handleDrop
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ height: "100%" }}>
|
<Flex sx={{ height: "100%", flexGrow: 1 }} {...containerListeners}>
|
||||||
<ImageDrop onDrop={handleDrop}>{children}</ImageDrop>
|
{children}
|
||||||
<ImageTypeModal
|
{dragging && (
|
||||||
isOpen={isImageTypeModalOpen}
|
<Flex
|
||||||
onMaps={handleMaps}
|
sx={{
|
||||||
onTokens={handleTokens}
|
position: "absolute",
|
||||||
onRequestClose={handleImageTypeClose}
|
top: 0,
|
||||||
multiple={droppedImagesRef.current?.length > 1}
|
right: 0,
|
||||||
/>
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
cursor: "copy",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
{...overlayListeners}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
bg="overlay"
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "20%",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
opacity: droppingType === "maps" ? 1 : 0.5,
|
||||||
|
}}
|
||||||
|
onDragEnter={handleMapsOver}
|
||||||
|
>
|
||||||
|
<Text sx={{ pointerEvents: "none" }}>
|
||||||
|
{"Drop image to import as a map"}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
bg="overlay"
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "80%",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
opacity: droppingType === "tokens" ? 1 : 0.5,
|
||||||
|
}}
|
||||||
|
onDragEnter={handleTokensOver}
|
||||||
|
>
|
||||||
|
<Text sx={{ pointerEvents: "none" }}>
|
||||||
|
{"Drop image to import as a token"}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={isLargeImageWarningModalOpen}
|
isOpen={isLargeImageWarningModalOpen}
|
||||||
onRequestClose={handleLargeImageWarningCancel}
|
onRequestClose={handleLargeImageWarningCancel}
|
||||||
@ -115,7 +169,7 @@ function GlobalImageDrop({ children }) {
|
|||||||
description="An imported image is larger than 20MB, this may cause slowness. Continue?"
|
description="An imported image is larger than 20MB, this may cause slowness. Continue?"
|
||||||
/>
|
/>
|
||||||
{isLoading && <LoadingOverlay bg="overlay" />}
|
{isLoading && <LoadingOverlay bg="overlay" />}
|
||||||
</Box>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
37
src/components/image/ImageDrop.js
Normal file
37
src/components/image/ImageDrop.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Box, Flex, Text } from "theme-ui";
|
||||||
|
|
||||||
|
import useImageDrop from "../../hooks/useImageDrop";
|
||||||
|
|
||||||
|
function ImageDrop({ onDrop, dropText, children }) {
|
||||||
|
const { dragging, containerListeners, overlayListeners } = useImageDrop(
|
||||||
|
onDrop
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Box {...containerListeners}>
|
||||||
|
{children}
|
||||||
|
{dragging && (
|
||||||
|
<Flex
|
||||||
|
bg="overlay"
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "copy",
|
||||||
|
}}
|
||||||
|
{...overlayListeners}
|
||||||
|
>
|
||||||
|
<Text sx={{ pointerEvents: "none" }}>
|
||||||
|
{dropText || "Drop image to import"}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageDrop;
|
@ -1,26 +1,34 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Box, Flex, Text } from "theme-ui";
|
|
||||||
import { useToasts } from "react-toast-notifications";
|
import { useToasts } from "react-toast-notifications";
|
||||||
|
|
||||||
const supportFileTypes = ["image/jpeg", "image/gif", "image/png", "image/webp"];
|
import Vector2 from "../helpers/Vector2";
|
||||||
|
|
||||||
function ImageDrop({ onDrop, dropText, children }) {
|
function useImageDrop(
|
||||||
|
onImageDrop,
|
||||||
|
supportFileTypes = ["image/jpeg", "image/gif", "image/png", "image/webp"]
|
||||||
|
) {
|
||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
|
|
||||||
const [dragging, setDragging] = useState(false);
|
const [dragging, setDragging] = useState(false);
|
||||||
function handleImageDragEnter(event) {
|
function onDragEnter(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleImageDragLeave(event) {
|
function onDragLeave(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setDragging(false);
|
setDragging(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleImageDrop(event) {
|
function onDragOver(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.dataTransfer.dropEffect = "copy";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDrop(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
let imageFiles = [];
|
let imageFiles = [];
|
||||||
@ -63,41 +71,15 @@ function ImageDrop({ onDrop, dropText, children }) {
|
|||||||
addToast(`Unsupported file type for ${file.name}`);
|
addToast(`Unsupported file type for ${file.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDrop(imageFiles);
|
const dropPosition = new Vector2(event.clientX, event.clientY);
|
||||||
|
onImageDrop(imageFiles, dropPosition);
|
||||||
setDragging(false);
|
setDragging(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const containerListeners = { onDragEnter };
|
||||||
<Box onDragEnter={handleImageDragEnter} sx={{ height: "100%" }}>
|
const overlayListeners = { onDragLeave, onDragOver, onDrop };
|
||||||
{children}
|
|
||||||
{dragging && (
|
return { dragging, containerListeners, overlayListeners };
|
||||||
<Flex
|
|
||||||
bg="overlay"
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
cursor: "copy",
|
|
||||||
}}
|
|
||||||
onDragLeave={handleImageDragLeave}
|
|
||||||
onDragOver={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
e.dataTransfer.dropEffect = "copy";
|
|
||||||
}}
|
|
||||||
onDrop={handleImageDrop}
|
|
||||||
>
|
|
||||||
<Text sx={{ pointerEvents: "none" }}>
|
|
||||||
{dropText || "Drop image to import"}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImageDrop;
|
export default useImageDrop;
|
@ -1,34 +0,0 @@
|
|||||||
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;
|
|
@ -9,7 +9,7 @@ import ConfirmModal from "./ConfirmModal";
|
|||||||
import Modal from "../components/Modal";
|
import Modal from "../components/Modal";
|
||||||
import LoadingOverlay from "../components/LoadingOverlay";
|
import LoadingOverlay from "../components/LoadingOverlay";
|
||||||
|
|
||||||
import ImageDrop from "../components/file/ImageDrop";
|
import ImageDrop from "../components/image/ImageDrop";
|
||||||
|
|
||||||
import MapTiles from "../components/map/MapTiles";
|
import MapTiles from "../components/map/MapTiles";
|
||||||
import MapEditBar from "../components/map/MapEditBar";
|
import MapEditBar from "../components/map/MapEditBar";
|
||||||
|
@ -9,7 +9,7 @@ import ConfirmModal from "./ConfirmModal";
|
|||||||
import Modal from "../components/Modal";
|
import Modal from "../components/Modal";
|
||||||
import LoadingOverlay from "../components/LoadingOverlay";
|
import LoadingOverlay from "../components/LoadingOverlay";
|
||||||
|
|
||||||
import ImageDrop from "../components/file/ImageDrop";
|
import ImageDrop from "../components/image/ImageDrop";
|
||||||
|
|
||||||
import TokenTiles from "../components/token/TokenTiles";
|
import TokenTiles from "../components/token/TokenTiles";
|
||||||
import TokenEditBar from "../components/token/TokenEditBar";
|
import TokenEditBar from "../components/token/TokenEditBar";
|
||||||
|
@ -20,6 +20,8 @@ import Session from "./Session";
|
|||||||
import Map from "../components/map/Map";
|
import Map from "../components/map/Map";
|
||||||
import TokenBar from "../components/token/TokenBar";
|
import TokenBar from "../components/token/TokenBar";
|
||||||
|
|
||||||
|
import GlobalImageDrop from "../components/image/GlobalImageDrop";
|
||||||
|
|
||||||
const defaultMapActions = {
|
const defaultMapActions = {
|
||||||
mapDrawActions: [],
|
mapDrawActions: [],
|
||||||
mapDrawActionIndex: -1,
|
mapDrawActionIndex: -1,
|
||||||
@ -457,7 +459,7 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<GlobalImageDrop>
|
||||||
<Map
|
<Map
|
||||||
map={currentMap}
|
map={currentMap}
|
||||||
mapState={currentMapState}
|
mapState={currentMapState}
|
||||||
@ -482,7 +484,7 @@ function NetworkedMapAndTokens({ session }) {
|
|||||||
session={session}
|
session={session}
|
||||||
/>
|
/>
|
||||||
<TokenBar onMapTokensStateCreate={handleMapTokensStateCreate} />
|
<TokenBar onMapTokensStateCreate={handleMapTokensStateCreate} />
|
||||||
</>
|
</GlobalImageDrop>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import OfflineBanner from "../components/banner/OfflineBanner";
|
|||||||
import LoadingOverlay from "../components/LoadingOverlay";
|
import LoadingOverlay from "../components/LoadingOverlay";
|
||||||
import Link from "../components/Link";
|
import Link from "../components/Link";
|
||||||
import MapLoadingOverlay from "../components/map/MapLoadingOverlay";
|
import MapLoadingOverlay from "../components/map/MapLoadingOverlay";
|
||||||
import GlobalImageDrop from "../components/file/GlobalImageDrop";
|
|
||||||
|
|
||||||
import AuthModal from "../modals/AuthModal";
|
import AuthModal from "../modals/AuthModal";
|
||||||
import GameExpiredModal from "../modals/GameExpiredModal";
|
import GameExpiredModal from "../modals/GameExpiredModal";
|
||||||
@ -115,18 +114,16 @@ function Game() {
|
|||||||
<PlayerProvider session={session}>
|
<PlayerProvider session={session}>
|
||||||
<PartyProvider session={session}>
|
<PartyProvider session={session}>
|
||||||
<MapStageProvider value={mapStageRef}>
|
<MapStageProvider value={mapStageRef}>
|
||||||
<GlobalImageDrop>
|
<Flex
|
||||||
<Flex
|
sx={{
|
||||||
sx={{
|
justifyContent: "space-between",
|
||||||
justifyContent: "space-between",
|
flexGrow: 1,
|
||||||
flexGrow: 1,
|
height: "100%",
|
||||||
height: "100%",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<NetworkedParty session={session} gameId={gameId} />
|
||||||
<NetworkedParty session={session} gameId={gameId} />
|
<NetworkedMapAndTokens session={session} />
|
||||||
<NetworkedMapAndTokens session={session} />
|
</Flex>
|
||||||
</Flex>
|
|
||||||
</GlobalImageDrop>
|
|
||||||
<Banner
|
<Banner
|
||||||
isOpen={!!peerError}
|
isOpen={!!peerError}
|
||||||
onRequestClose={() => setPeerError(null)}
|
onRequestClose={() => setPeerError(null)}
|
||||||
|
Loading…
Reference in New Issue
Block a user