Browse Source

Add ability to drop and paste images

Jeremie Pardou-Piquemal 3 years ago
parent
commit
ad01118837

+ 2 - 0
.gitignore

@@ -21,3 +21,5 @@
 npm-debug.log*
 yarn-debug.log*
 yarn-error.log*
+
+.cache

+ 1 - 2
src/App.js

@@ -17,8 +17,7 @@ import BoardView from "./views/BoardView";
 
 import Waiter from "./components/Waiter";
 
-const SOCKET_URL = process.env.REACT_APP_SOCKET_URL || "http://localhost:4000";
-const SOCKET_PATH = process.env.REACT_APP_SOCKET_PATH || "/socket.io";
+import { SOCKET_URL, SOCKET_PATH } from "./utils/settings";
 
 const SOCKET_OPTIONS = {
   forceNew: true,

+ 1 - 0
src/components/Board/Board.js

@@ -16,6 +16,7 @@ const Placeholder = styled.p`
   top: 40vh;
   width: 100vw;
   text-align: center;
+  color: hsl(0, 0%, 70%);
 `;
 
 const StyledBoard = styled.div.attrs(() => ({ className: "board" }))`

+ 3 - 15
src/components/Board/Form/ImageField.js

@@ -2,15 +2,10 @@ import React from "react";
 import { useTranslation } from "react-i18next";
 import styled from "styled-components";
 import { useDropzone } from "react-dropzone";
-
-const API_ENDPOINT =
-  process.env.REACT_APP_API_ENDPOINT || "http://localhost:3001";
-
-const uploadURI = `${API_ENDPOINT}/upload`;
+import { uploadImage } from "../../../utils/api";
 
 const Thumbnail = styled.img`
   height: 50px;
-  max-width: 50px;
 `;
 
 const RemoveButton = styled.span`
@@ -27,16 +22,9 @@ const ImageField = ({ value, onChange }) => {
     async (acceptedFiles) => {
       const file = acceptedFiles[0];
       setUploading(true);
-      const payload = new FormData();
-      payload.append("file", file);
-      const result = await fetch(uploadURI, {
-        method: "POST",
-        body: payload, // this sets the `Content-Type` header to `multipart/form-data`
-      });
-
-      const location = await result.text();
-      setUploading(false);
+      const location = await uploadImage(file);
       onChange(location);
+      setUploading(false);
     },
     [onChange]
   );

+ 79 - 0
src/components/ImageDropNPaste.js

@@ -0,0 +1,79 @@
+import React from "react";
+
+import Waiter from "./Waiter";
+import { useDropzone } from "react-dropzone";
+import { uploadImage } from "../utils/api";
+import { PanZoomRotateAtom } from "./Board";
+import { useItems } from "../components/Board/Items";
+import { nanoid } from "nanoid";
+
+import { useRecoilCallback } from "recoil";
+
+const ImageDropNPaste = ({ children }) => {
+  const [uploading, setUploading] = React.useState(false);
+  const { pushItem } = useItems();
+
+  const addImageItem = useRecoilCallback(
+    ({ snapshot }) => async (location) => {
+      const { centerX, centerY } = await snapshot.getPromise(PanZoomRotateAtom);
+      pushItem({
+        type: "image",
+        x: centerX,
+        y: centerY,
+        id: nanoid(),
+        content: location,
+      });
+    },
+    [pushItem]
+  );
+
+  const onDrop = React.useCallback(
+    async (acceptedFiles) => {
+      setUploading(true);
+      await Promise.all(
+        acceptedFiles.map(async (file) => {
+          const location = await uploadImage(file);
+          await addImageItem(location);
+        })
+      );
+      setUploading(false);
+    },
+    [addImageItem]
+  );
+
+  const { getRootProps } = useDropzone({ onDrop });
+
+  const onPaste = React.useCallback(
+    async (e) => {
+      const items = e.clipboardData.items;
+      setUploading(true);
+      for (var i = 0; i < items.length; i++) {
+        const item = items[i];
+        if (item.type.indexOf("image") !== -1) {
+          const file = item.getAsFile();
+          const location = await uploadImage(file);
+          await addImageItem(location);
+        }
+      }
+      setUploading(false);
+    },
+    [addImageItem]
+  );
+
+  React.useEffect(() => {
+    window.addEventListener("paste", onPaste, false);
+
+    return () => {
+      window.removeEventListener("paste", onPaste);
+    };
+  }, [onPaste]);
+
+  return (
+    <div {...getRootProps()}>
+      {children}
+      {uploading && <Waiter message="Uploading image(s)..." />}
+    </div>
+  );
+};
+
+export default ImageDropNPaste;

+ 1 - 1
src/components/Waiter.js

@@ -8,7 +8,7 @@ const Overlay = styled.div`
   height: 100vh;
   top: 0;
   left: 0;
-  background-color: #202b38;
+  background-color: hsla(227, 20%, 20%, 0.9);
   color: #606984;
   display: flex;
   justify-content: center;

+ 14 - 0
src/utils/api.js

@@ -0,0 +1,14 @@
+import { API_ENDPOINT } from "./settings";
+
+const uploadURI = `${API_ENDPOINT}/upload`;
+
+export const uploadImage = async (file) => {
+  const payload = new FormData();
+  payload.append("file", file);
+  const result = await fetch(uploadURI, {
+    method: "POST",
+    body: payload, // this sets the `Content-Type` header to `multipart/form-data`
+  });
+
+  return await result.text();
+};

+ 9 - 0
src/utils/settings.js

@@ -0,0 +1,9 @@
+export const API_ENDPOINT =
+  process.env.REACT_APP_API_ENDPOINT || "http://localhost:3001";
+
+export const SOCKET_URL =
+  process.env.REACT_APP_SOCKET_URL || "http://localhost:4000";
+
+export const SOCKET_PATH = process.env.REACT_APP_SOCKET_PATH || "/socket.io";
+
+export const SHOW_WELCOME = process.env.REACT_APP_NO_WELCOME !== "1";

+ 4 - 4
src/views/BoardView.js

@@ -1,6 +1,7 @@
 import React from "react";
 import styled from "styled-components";
 
+import { SHOW_WELCOME } from "../utils/settings";
 import BoardMenu from "../components/BoardMenu";
 import GameController from "../components/GameController";
 import SubscribeGameEvents from "../components/SubscribeGameEvents";
@@ -13,6 +14,7 @@ import WelcomeModal from "../components/WelcomeModal";
 import InfoModal from "../components/InfoModal";
 import NavBar from "../components/NavBar";
 import AutoSave from "../components/AutoSave";
+import ImageDropNPaste from "../components/ImageDropNPaste";
 
 const BoardContainer = styled.div`
   width: 100%;
@@ -22,8 +24,6 @@ const BoardContainer = styled.div`
   background-color: #202b38;
 `;
 
-const SHOW_WELCOME = process.env.REACT_APP_NO_WELCOME !== "1";
-
 export const BoardView = () => {
   const { currentUser, users } = useUsers();
   const [showLoadGameModal, setShowLoadGameModal] = React.useState(false);
@@ -34,7 +34,7 @@ export const BoardView = () => {
   const [edit, setEdit] = React.useState(false);
 
   return (
-    <>
+    <ImageDropNPaste>
       <SubscribeUserEvents />
       <SubscribeGameEvents />
       <AutoSave />
@@ -65,7 +65,7 @@ export const BoardView = () => {
       <HelpModal show={showHelpModal} setShow={setShowHelpModal} />
       <InfoModal show={showInfoModal} setShow={setShowInfoModal} />
       <WelcomeModal show={showWelcomeModal} setShow={setShowWelcomeModal} />
-    </>
+    </ImageDropNPaste>
   );
 };