Convert image drop to typescript

This commit is contained in:
Mitchell McCaffrey 2021-07-09 17:19:00 +10:00
parent f6d695a48a
commit 72b6994a2e
3 changed files with 104 additions and 71 deletions

View File

@ -20,9 +20,23 @@ import { useTokenData } from "../../contexts/TokenDataContext";
import { useAssets } from "../../contexts/AssetsContext"; import { useAssets } from "../../contexts/AssetsContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import useImageDrop from "../../hooks/useImageDrop"; import useImageDrop, { ImageDropEvent } from "../../hooks/useImageDrop";
function GlobalImageDrop({ children, onMapChange, onMapTokensStateCreate }) { import { Map } from "../../types/Map";
import { MapState } from "../../types/MapState";
import { TokenState } from "../../types/TokenState";
type GlobalImageDropProps = {
children?: React.ReactNode;
onMapChange: (map: Map, mapState: MapState) => void;
onMapTokensStateCreate: (states: TokenState[]) => void;
};
function GlobalImageDrop({
children,
onMapChange,
onMapTokensStateCreate,
}: GlobalImageDropProps) {
const { addToast } = useToasts(); const { addToast } = useToasts();
const userId = useUserId(); const userId = useUserId();
@ -32,17 +46,15 @@ function GlobalImageDrop({ children, onMapChange, onMapTokensStateCreate }) {
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
const [isLargeImageWarningModalOpen, setShowLargeImageWarning] = useState( const [isLargeImageWarningModalOpen, setShowLargeImageWarning] =
false useState(false);
);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const droppedImagesRef = useRef(); const droppedImagesRef = useRef<File[]>();
const dropPositionRef = useRef(); const dropPositionRef = useRef<Vector2>();
// maps or tokens const [droppingType, setDroppingType] = useState<"maps" | "tokens">("maps");
const [droppingType, setDroppingType] = useState("maps");
async function handleDrop(files, dropPosition) { async function handleDrop({ files, dropPosition }: ImageDropEvent) {
if (navigator.storage) { if (navigator.storage) {
// Attempt to enable persistant storage // Attempt to enable persistant storage
await navigator.storage.persist(); await navigator.storage.persist();
@ -87,55 +99,63 @@ function GlobalImageDrop({ children, onMapChange, onMapTokensStateCreate }) {
} }
async function handleMaps() { async function handleMaps() {
setIsLoading(true); if (droppedImagesRef.current && userId) {
let maps = []; setIsLoading(true);
for (let file of droppedImagesRef.current) { let maps = [];
const { map, assets } = await createMapFromFile(file, userId); for (let file of droppedImagesRef.current) {
await addMap(map); const { map, assets } = await createMapFromFile(file, userId);
await addAssets(assets); await addMap(map);
maps.push(map); await addAssets(assets);
} maps.push(map);
}
// Change map if only 1 dropped // Change map if only 1 dropped
if (maps.length === 1) { if (maps.length === 1) {
const mapState = await getMapState(maps[0].id); const mapState = await getMapState(maps[0].id);
onMapChange(maps[0], mapState); onMapChange(maps[0], mapState);
} }
setIsLoading(false); setIsLoading(false);
droppedImagesRef.current = undefined; droppedImagesRef.current = undefined;
}
} }
async function handleTokens() { async function handleTokens() {
setIsLoading(true); if (droppedImagesRef.current && userId) {
// Keep track of tokens so we can add them to the map setIsLoading(true);
let tokens = []; // Keep track of tokens so we can add them to the map
for (let file of droppedImagesRef.current) { let tokens = [];
const { token, assets } = await createTokenFromFile(file, userId); for (let file of droppedImagesRef.current) {
await addToken(token); const { token, assets } = await createTokenFromFile(file, userId);
await addAssets(assets); await addToken(token);
tokens.push(token); await addAssets(assets);
} tokens.push(token);
setIsLoading(false); }
droppedImagesRef.current = undefined; setIsLoading(false);
droppedImagesRef.current = undefined;
const dropPosition = dropPositionRef.current; const dropPosition = dropPositionRef.current;
const mapStage = mapStageRef.current; const mapStage = mapStageRef.current;
if (mapStage && dropPosition) { if (mapStage && dropPosition) {
const mapPosition = clientPositionToMapPosition(mapStage, dropPosition); const mapPosition = clientPositionToMapPosition(mapStage, dropPosition);
if (mapPosition) { if (mapPosition) {
let tokenStates = []; let tokenStates = [];
let offset = new Vector2(0, 0); let offset = new Vector2(0, 0);
for (let token of tokens) { for (let token of tokens) {
if (token) { if (token) {
tokenStates.push( tokenStates.push(
createTokenState(token, Vector2.add(mapPosition, offset), userId) createTokenState(
); token,
offset = Vector2.add(offset, 0.01); Vector2.add(mapPosition, offset),
userId
)
);
offset = Vector2.add(offset, 0.01);
}
}
if (tokenStates.length > 0) {
onMapTokensStateCreate(tokenStates);
} }
}
if (tokenStates.length > 0) {
onMapTokensStateCreate(tokenStates);
} }
} }
} }
@ -149,9 +169,8 @@ function GlobalImageDrop({ children, onMapChange, onMapTokensStateCreate }) {
setDroppingType("tokens"); setDroppingType("tokens");
} }
const { dragging, containerListeners, overlayListeners } = useImageDrop( const { dragging, containerListeners, overlayListeners } =
handleDrop useImageDrop(handleDrop);
);
return ( return (
<Flex sx={{ height: "100%", flexGrow: 1 }} {...containerListeners}> <Flex sx={{ height: "100%", flexGrow: 1 }} {...containerListeners}>

View File

@ -1,12 +1,17 @@
import React from "react"; import React from "react";
import { Box, Flex, Text } from "theme-ui"; import { Box, Flex, Text } from "theme-ui";
import useImageDrop from "../../hooks/useImageDrop"; import useImageDrop, { ImageDropEvent } from "../../hooks/useImageDrop";
function ImageDrop({ onDrop, dropText, children }) { type ImageDropProps = {
const { dragging, containerListeners, overlayListeners } = useImageDrop( onDrop: (event: ImageDropEvent) => void;
onDrop dropText: string;
); children?: React.ReactNode;
};
function ImageDrop({ onDrop, dropText, children }: ImageDropProps) {
const { dragging, containerListeners, overlayListeners } =
useImageDrop(onDrop);
return ( return (
<Box {...containerListeners}> <Box {...containerListeners}>
{children} {children}

View File

@ -3,41 +3,51 @@ import { useToasts } from "react-toast-notifications";
import Vector2 from "../helpers/Vector2"; import Vector2 from "../helpers/Vector2";
export type ImageDropEvent = {
files: File[];
dropPosition: Vector2;
};
function useImageDrop( function useImageDrop(
onImageDrop, onImageDrop: (event: ImageDropEvent) => void,
supportFileTypes = ["image/jpeg", "image/gif", "image/png", "image/webp"] 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 onDragEnter(event) { function onDragEnter(event: React.DragEvent<HTMLDivElement>) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setDragging(true); setDragging(true);
} }
function onDragLeave(event) { function onDragLeave(event: React.DragEvent<HTMLDivElement>) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setDragging(false); setDragging(false);
} }
function onDragOver(event) { function onDragOver(event: React.DragEvent<HTMLDivElement>) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
event.dataTransfer.dropEffect = "copy"; if (event.dataTransfer) {
event.dataTransfer.dropEffect = "copy";
}
} }
async function onDrop(event) { async function onDrop(event: React.DragEvent<HTMLDivElement>) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
let imageFiles = []; let imageFiles = [];
// Check if the dropped image is from a URL // Check if the dropped image is from a URL
const html = event.dataTransfer.getData("text/html"); const html = event.dataTransfer?.getData("text/html");
if (html) { if (html) {
try { try {
const urlMatch = html.match(/src="?([^"\s]+)"?\s*/); const urlMatch = html.match(/src="?([^"\s]+)"?\s*/);
if (!urlMatch) {
throw new Error("Unable to find image source");
}
const url = urlMatch[1].replace("&amp;", "&"); // Reverse html encoding of url parameters const url = urlMatch[1].replace("&amp;", "&"); // Reverse html encoding of url parameters
let name = ""; let name = "";
const altMatch = html.match(/alt="?([^"]+)"?\s*/); const altMatch = html.match(/alt="?([^"]+)"?\s*/);
@ -46,8 +56,7 @@ function useImageDrop(
} }
const response = await fetch(url); const response = await fetch(url);
if (response.ok) { if (response.ok) {
const file = await response.blob(); const file = new File([await response.blob()], name);
file.name = name;
if (supportFileTypes.includes(file.type)) { if (supportFileTypes.includes(file.type)) {
imageFiles.push(file); imageFiles.push(file);
} else { } else {
@ -63,7 +72,7 @@ function useImageDrop(
} }
} }
const files = event.dataTransfer.files; const files = event.dataTransfer?.files || [];
for (let file of files) { for (let file of files) {
if (supportFileTypes.includes(file.type)) { if (supportFileTypes.includes(file.type)) {
imageFiles.push(file); imageFiles.push(file);
@ -72,7 +81,7 @@ function useImageDrop(
} }
} }
const dropPosition = new Vector2(event.clientX, event.clientY); const dropPosition = new Vector2(event.clientX, event.clientY);
onImageDrop(imageFiles, dropPosition); onImageDrop({ files: imageFiles, dropPosition });
setDragging(false); setDragging(false);
} }