Browse Source

modify import from url implementation

AykutSarac 3 years ago
parent
commit
7be8e3dbee

+ 17 - 41
src/components/Sidebar/index.tsx

@@ -4,8 +4,6 @@ import Link from "next/link";
 import styled from "styled-components";
 import { CanvasDirection } from "reaflow";
 import { TiFlowMerge } from "react-icons/ti";
-import { BsList } from "react-icons/bs";
-import { MdUploadFile } from "react-icons/md";
 import { RiPatreonFill } from "react-icons/ri";
 import { CgArrowsMergeAltH, CgArrowsShrinkH } from "react-icons/cg";
 import {
@@ -14,23 +12,22 @@ import {
   AiOutlineTwitter,
   AiOutlineSave,
   AiOutlineFileAdd,
-  AiOutlineLink,
 } from "react-icons/ai";
 
 import { Tooltip } from "src/components/Tooltip";
-import { Modal } from "src/components/Modal/index";
 import { ConfigActionType } from "src/reducer/reducer";
 import { useConfig } from "src/hocs/config";
 import { useRouter } from "next/router";
+import { ImportModal } from "src/containers/ImportModal";
 
 const StyledSidebar = styled.div`
   display: flex;
   justify-content: space-between;
   flex-direction: column;
   align-items: center;
-  width: 36px;
+  width: fit-content;
   background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
-  padding: 8px;
+  padding: 4px;
   border-right: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
 `;
 
@@ -39,19 +36,23 @@ const StyledElement = styled.div`
   justify-content: center;
   text-align: center;
   font-size: 28px;
-  font-weight: 700;
+  font-weight: 600;
   width: 100%;
   color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
   cursor: pointer;
 
-  &:hover :is(a, svg) {
-    color: ${({ theme }) => theme.INTERACTIVE_HOVER};
-  }
-
   svg {
-    padding: 8px 0;
+    padding: 8px;
     vertical-align: middle;
   }
+
+  a {
+    display: flex;
+  }
+
+  &:hover :is(a, svg) {
+    color: ${({ theme }) => theme.INTERACTIVE_HOVER};
+  }
 `;
 
 const StyledText = styled.span<{ secondary?: boolean }>`
@@ -92,14 +93,6 @@ const StyledLogo = styled.div`
   color: ${({ theme }) => theme.FULL_WHITE};
 `;
 
-const StyledImportFile = styled.label`
-  cursor: pointer;
-
-  input[type="file"] {
-    display: none;
-  }
-`;
-
 function rotateLayout(layout: CanvasDirection) {
   if (layout === "LEFT") return 90;
   if (layout === "UP") return 180;
@@ -111,16 +104,12 @@ export const Sidebar: React.FC = () => {
   const { json, settings, dispatch } = useConfig();
   const router = useRouter();
   const [jsonFile, setJsonFile] = React.useState<File | null>(null);
-  const [visible, setVisible] = React.useState(false);
+  const [modalVisible, setModalVisible] = React.useState(false);
 
   const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
     if (e.target.files) setJsonFile(e.target.files?.item(0));
   };
 
-  const handleImportJSONFromURL = () => {
-    setVisible(true);
-  };
-
   const handleClear = () => {
     dispatch({ type: ConfigActionType.SET_JSON, payload: "{}" });
     localStorage.removeItem("json");
@@ -154,7 +143,7 @@ export const Sidebar: React.FC = () => {
   return (
     <StyledSidebar>
       <StyledTopWrapper>
-        <Modal visible={visible} setVisible={setVisible} />
+        <ImportModal visible={modalVisible} setVisible={setModalVisible} />
         <Link passHref href="/">
           <StyledElement onClick={() => router.push("/")}>
             <StyledLogo>
@@ -164,21 +153,8 @@ export const Sidebar: React.FC = () => {
           </StyledElement>
         </Link>
         <Tooltip title="Import File">
-          <StyledElement>
-            <StyledImportFile>
-              <input
-                key={jsonFile?.name}
-                onChange={handleFileChange}
-                type="file"
-                accept="application/JSON"
-              />
-              <AiOutlineFileAdd />
-            </StyledImportFile>
-          </StyledElement>
-        </Tooltip>
-        <Tooltip title="Fetch JSON from URL">
-          <StyledElement onClick={handleImportJSONFromURL}>
-            <AiOutlineLink />
+          <StyledElement onClick={() => setModalVisible(true)}>
+            <AiOutlineFileAdd />
           </StyledElement>
         </Tooltip>
         <Tooltip title="Rotate Layout">

+ 2 - 2
src/containers/Editor/Tools.tsx

@@ -8,7 +8,7 @@ import {
 import { FiDownload } from "react-icons/fi";
 import { HiOutlineSun, HiOutlineMoon } from "react-icons/hi";
 import { MdCenterFocusWeak } from "react-icons/md";
-import { Input } from "src/components/Input";
+import { SearchInput } from "src/containers/SearchInput";
 import { useConfig } from "src/hocs/config";
 import { ConfigActionType } from "src/reducer/reducer";
 import styled from "styled-components";
@@ -68,7 +68,7 @@ export const Tools: React.FC = () => {
       <StyledToolElement aria-label="switch theme" onClick={toggleTheme}>
         {settings.lightmode ? <HiOutlineMoon /> : <HiOutlineSun />}
       </StyledToolElement>
-      <Input />
+      <SearchInput />
       <StyledToolElement aria-label="save" onClick={exportAsImage}>
         <FiDownload />
       </StyledToolElement>

+ 134 - 0
src/containers/ImportModal/index.tsx

@@ -0,0 +1,134 @@
+import React from "react";
+import styled from "styled-components";
+import toast from "react-hot-toast";
+
+import { useConfig } from "src/hocs/config";
+import { ConfigActionType } from "src/reducer/reducer";
+import { Modal } from "src/components/Modal";
+import { Button } from "src/components/Button";
+import { AiOutlineUpload } from "react-icons/ai";
+
+const StyledInput = styled.input`
+  background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
+  color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
+  outline: none;
+  border: none;
+  border-radius: 5px;
+  padding: 6px 8px;
+  width: 100%;
+  margin-bottom: 10px;
+`;
+
+const StyledModalContent = styled(Modal.Content)`
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+`;
+
+const StyledUploadWrapper = styled.label`
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background: ${({ theme }) => theme.BACKGROUND_SECONDARY};
+  border: 2px dashed ${({ theme }) => theme.BACKGROUND_TERTIARY};
+  border-radius: 5px;
+  width: 100%;
+  padding: 16px;
+  cursor: pointer;
+
+  input[type="file"] {
+    display: none;
+  }
+`;
+
+const StyledFileName = styled.span`
+  color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
+`;
+
+const StyledUploadMessage = styled.h3`
+  color: ${({ theme }) => theme.INTERACTIVE_ACTIVE};
+  margin-bottom: 0;
+`;
+
+export const ImportModal = ({ visible, setVisible }) => {
+  const { dispatch } = useConfig();
+  const [url, setURL] = React.useState("");
+  const [jsonFile, setJsonFile] = React.useState<File | null>(null);
+
+  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (e.target.files) setJsonFile(e.target.files?.item(0));
+  };
+
+  const handleImportFile = () => {
+    if (url) {
+      setJsonFile(null);
+
+      return fetch(url)
+        .then((res) => res.json())
+        .then((json) => {
+          dispatch({
+            type: ConfigActionType.SET_JSON,
+            payload: JSON.stringify(json),
+          });
+
+          setVisible(false);
+        })
+        .catch(() => toast.error("Failed to fetch JSON!"));
+    }
+
+    if (jsonFile) {
+      const reader = new FileReader();
+
+      reader.readAsText(jsonFile, "UTF-8");
+      reader.onload = function (data) {
+        dispatch({
+          type: ConfigActionType.SET_JSON,
+          payload: data.target?.result as string,
+        });
+      };
+    }
+  };
+
+  React.useEffect(() => {
+    return () => {
+      setJsonFile(null);
+      setURL("");
+    };
+  }, [visible]);
+
+  return (
+    <Modal visible={visible} setVisible={setVisible}>
+      <Modal.Header>Select An Import Method</Modal.Header>
+      <StyledModalContent>
+        <StyledInput
+          value={url}
+          onChange={(e) => setURL(e.target.value)}
+          type="url"
+          placeholder="URL of JSON to fetch"
+        />
+        <StyledUploadWrapper>
+          <input
+            key={jsonFile?.name}
+            onChange={handleFileChange}
+            type="file"
+            accept="application/JSON"
+          />
+          <AiOutlineUpload size={48} />
+          <StyledUploadMessage>Click Here to Upload JSON</StyledUploadMessage>
+          <StyledFileName>{jsonFile?.name ?? "None"}</StyledFileName>
+        </StyledUploadWrapper>
+      </StyledModalContent>
+      <Modal.Controls setVisible={setVisible}>
+        <Button
+          status="SECONDARY"
+          onClick={handleImportFile}
+          disabled={!(jsonFile || url)}
+        >
+          Import
+        </Button>
+      </Modal.Controls>
+    </Modal>
+  );
+};

+ 1 - 1
src/components/Input/index.tsx → src/containers/SearchInput/index.tsx

@@ -51,7 +51,7 @@ const StyledSearchButton = styled.button`
   }
 `;
 
-export const Input: React.FC = () => {
+export const SearchInput: React.FC = () => {
   const [content, setContent, skip] = useFocusNode();
 
   const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {

+ 1 - 1
src/pages/index.tsx

@@ -3,12 +3,12 @@ import { Button } from "src/components/Button";
 import { Container } from "src/components/Container";
 import { Navbar } from "src/components/Navbar";
 import { Image } from "src/components/Image";
-import styled from "styled-components";
 import { AiFillGithub } from "react-icons/ai";
 import { Footer } from "src/components/Footer";
 import Head from "next/head";
 import { Producthunt } from "src/components/Producthunt";
 import { useRouter } from "next/router";
+import styled from "styled-components";
 
 const StyledHome = styled.div`
   padding: 24px;