본문 바로가기
개발/React

React Material - Mui로 파일 브라우저 만들기 (React File Browser with Mui)

by 피로물든딸기 2023. 6. 2.
반응형

리액트 전체 링크

Node JS 전체 링크

 

참고

- 파일 브라우저 만들기 (React File Browser with chonky)

 

glob으로 파일, 폴더 목록 찾기
Mui Tree View로 파일, 폴더 뷰 만들기
Mui Tree View로 파일, 폴더 뷰 만들기 (with Node JS)
Mui로 파일 브라우저 만들기 
파일 브라우저 만들기 (with Node JS)
파일 브라우저에서 파일 다운로드하기

 

파일, 폴더 뷰를 만들었으니, 다음과 같이 리액트 파일 브라우저(File Browser)를 만들어보자.


그리드를 이용하여 폴더 뷰 / 브라우저 분할하기

 

이전 글에서 만든 TreeViewExample2를 복사해서 FileBrowser로 바꾼다.

그리고 div의 style에 display를 grid로 설정하고 아래와 같이 적절히 화면을 분할한다.

const FileBrowser = () => {
  const [treeInfo, setTreeInfo] = useState({});
  
  ...
  
  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "20% 1% auto",
        gridGap: "25px",
        width: "100%",
      }}
    >
      <div>
        <TreeView
          aria-label="file system navigator"
          defaultCollapseIcon={<ExpandMoreIcon />}
          defaultExpandIcon={<ChevronRightIcon />}
          sx={{ height: 500, overflowX: "hidden" }}
        >
          {makeTreeItem(treeInfo, "")}
        </TreeView>
      </div>
      <div style={{ borderRight: "2px solid black" }} />
      <div>
        <div>File Browser</div>
      </div>
    </div>
  );
};

 

TreeView / 수직선 / File Browser를 포함하는 부모의 div에 20% 1% auto 비율로 화면을 분할하였다.


FileUI Component 만들기

 

FileBrowser에서는 아래와 같이 여러 파일들의 절대경로가 props로 넘겨지게 된다.

여기서는 임시로 경로를 추가해보자.

      <div>
        <FileUI pathInfo={"test/folder/temp"}/>
        <FileUI pathInfo={"test/folder/abcdef.json"}/>
        <FileUI pathInfo={"test/folder/abc12341234.txt"}/>
        <FileUI pathInfo={"test/folder/abc12341234.txt"}/>
        <FileUI pathInfo={"test/folder/abc1234123412341234.txt"}/>
        <FileUI pathInfo={"test/folder/abc123412341234123412341234.json"}/>
        <FileUI pathInfo={"test/folder/text.csv"}/>
        <FileUI pathInfo={"test/folder/text.txt"}/>
        <FileUI pathInfo={"test/folder/text.txt"}/>
        <FileUI pathInfo={"test/folder/text.txt"}/>     
      </div>

 

FileUI는 다음과 같이 구성된다.

파일이 아닌 경우( = 확장자가 없는 경우)는 폴더 아이콘을, 그리고 txt와 json은 아래와 같이 분리하였다.

아이콘 밑에는 폴더/파일 이름이 나타나게 된다.

주어지는 pathInfo에서 "/"로 split을 한 후, 마지막 값을 취하면 폴더/파일 이름을 알 수 있다.

const getFileName = (path) => {
  if (path === undefined) return undefined;
  let spt = path.split("/");
  return spt[spt.length - 1];
};

 

그리고 위에서 얻은 fileName으로 Icon 모양을 결정하면 된다.

const getMuiIcon = (fileName) => {
  if (fileName === undefined) return <TextSnippetIcon sx={{ fontSize: 60 }} />;

  let spt = fileName.split(".");
  if (spt.length === 1)
    return <FolderIcon sx={{ fontSize: 60, left: "50%" }} />;
  if (spt[1] === "json")
    return <DataObjectICON sx={{ fontSize: 60, left: "50%" }} />;
  return <TextSnippetIcon sx={{ fontSize: 60, left: "50%" }} />;
};

 

FileUI는 다음과 같다.

const FileUI = ({ pathInfo }) => {
  return (
    <div
      style={{
        display: "inline-block",
        width: "110px",
        height: "120px",
        backgroundColor: "silver",
        textAlign: "center",
        margin: "10px",
        cursor: "pointer",
      }}
      onClick={(e) => console.log(e)}
    >
      {getMuiIcon(getFileName(pathInfo))}
      <div style={{ width: "110px", height: "40px", wordBreak: "break-all" }}>
        <span
          style={{
            fontSize: "12px",
            backgroundColor: "green",
            cursor: "pointer",
            display: "block",
            height: "50px",
          }}
        >
          {getFileName(pathInfo)}
        </span>
      </div>
    </div>
  );
};

 

위와 같이 css를 작성하면 파일 이름이 길어지는 경우 아래처럼 FileUI의 모양이 어긋나게 된다.

span에 입력된 text의 아래 기준으로 정렬이 되기 때문이다.

 

상위의 div에 verticalAligntop으로 맞춰주면 해결할 수 있다.

    <div
      style={{
        display: "inline-block",
        width: "110px",
        height: "120px",
        backgroundColor: "silver",
        textAlign: "center",
        margin: "10px",
        cursor: "pointer",
        verticalAlign: "top",
      }}
      onClick={(e) => console.log(e)}
    >

다음 글에서 Node JS로 경로를 가져온 후, 파일 브라우저에 연동해보자.

 

전체 코드는 다음과 같다.

 

FileBrowser.js

import React, { useEffect, useState } from "react";

import TreeView from "@mui/lab/TreeView";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import TreeItem from "@mui/lab/TreeItem";
import FileUI from "./FileUI";

let localData = {
  child: [
    {
      label: "D:",
      nodeId: 0,
      child: [
        {
          label: "github",
          nodeId: 1,
          child: [
            {
              label: "globfiles",
              nodeId: 2,
              child: [
                {
                  label: "abc1",
                  nodeId: 3,
                  child: [
                    { label: "abc1_jsonfile1.json", nodeId: 4 },
                    { label: "abc1_jsonfile2.json", nodeId: 5 },
                    { label: "abc1_textfile1.txt", nodeId: 6 },
                    { label: "abc1_textfile2.txt", nodeId: 7 },
                    {
                      label: "abc2",
                      nodeId: 8,
                      child: [
                        { label: "abc2_jsonfile.json", nodeId: 12 },
                        {
                          label: "abc3",
                          nodeId: 13,
                          child: [
                            { label: "abc3_jsonfile.json", nodeId: 14 },
                            { label: "abc3_textfile.txt", nodeId: 15 },
                          ],
                        },
                      ],
                    },
                    {
                      label: "abc2_2",
                      nodeId: 9,
                      child: [
                        { label: "abc2_2_jsonfile.json", nodeId: 10 },
                        { label: "abc2_2_textfile.txt", nodeId: 11 },
                      ],
                    },
                  ],
                },
                {
                  label: "def1",
                  nodeId: 16,
                  child: [
                    { label: "def_jsonfile1.json", nodeId: 17 },
                    { label: "def_jsonfile2.json", nodeId: 18 },
                    { label: "def_textfile1.txt", nodeId: 19 },
                    { label: "def_textfile2.txt", nodeId: 20 },
                  ],
                },
                {
                  label: "ghi1",
                  nodeId: 21,
                  child: [
                    {
                      label: "ghi2",
                      nodeId: 22,
                      child: [
                        { label: "ghi2_jsonfile1.json", nodeId: 23 },
                        { label: "ghi2_jsonfile2.json", nodeId: 24 },
                        { label: "ghi2_textfile1.txt", nodeId: 25 },
                        { label: "ghi2_textfile2.txt", nodeId: 26 },
                      ],
                    },
                  ],
                },
                { label: "jsonfile1.json", nodeId: 27 },
                { label: "jsonfile2.json", nodeId: 28 },
                { label: "textfile1.txt", nodeId: 29 },
                { label: "textfile2.txt", nodeId: 30 },
              ],
            },
          ],
        },
      ],
    },
  ],
};

const FileBrowser = () => {
  const [treeInfo, setTreeInfo] = useState({});

  /*
  let tmpTreeInfo = {};
  let nodeId = 0;
  const appendChild = (arr, info) => {
    if (arr.child === undefined) arr.child = [];
    if (arr.child.findIndex((item) => item.label === info) === -1) {
      arr.child.push({ label: info, nodeId });
      nodeId++;
    }
  };

  const makeDirectories = (directories) => {
    tmpTreeInfo = {};
    for (let d of directories) {
      let split = d.split("/");
      let len = split.length;
      let current = tmpTreeInfo;

      for (let i = 0; i < len; i++) {
        appendChild(current, split[i]);
        current = current.child.find((item) => item.label === split[i]);
      }
    }

    console.log(tmpTreeInfo);
    setTreeInfo(tmpTreeInfo);
  };
  */

  const getFiles = () => {
    setTreeInfo(localData);
    return;
    // let server = `http://192.168.55.120:3002`;
    // let path = `D:\\github\\globfiles\\**`;
    // fetch(`${server}/useGlob?path=${path}`)
    //   .then((res) => res.json())
    //   .then((data) => makeDirectories(data.findPath));
  };

  const makeTreeItem = (info, parent) => {
    if (info.child === undefined) return;

    return info.child.map((item, idx) => (
      <TreeItem
        key={idx}
        nodeId={item.nodeId.toString()}
        label={item.label}
        onClick={() => console.log(`${parent}/${item.label}`)}
      >
        {makeTreeItem(item, `${parent}/${item.label}`)}
      </TreeItem>
    ));
  };

  useEffect(() => {
    getFiles();
  }, []);

  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "20% 1% auto",
        gridGap: "25px",
        width: "100%",
      }}
    >
      <div>
        <TreeView
          aria-label="file system navigator"
          defaultCollapseIcon={<ExpandMoreIcon />}
          defaultExpandIcon={<ChevronRightIcon />}
          sx={{ height: 500, overflowX: "hidden" }}
        >
          {makeTreeItem(treeInfo, "")}
        </TreeView>
      </div>
      <div style={{ borderRight: "2px solid black" }} />
      <div>
        <FileUI pathInfo={"test/folder/temp"}/>
        <FileUI pathInfo={"test/folder/abcdef.json"}/>
        <FileUI pathInfo={"test/folder/abc12341234.txt"}/>
        <FileUI pathInfo={"test/folder/abc12341234.txt"}/>
        <FileUI pathInfo={"test/folder/abc1234123412341234.txt"}/>
        <FileUI pathInfo={"test/folder/abc123412341234123412341234.json"}/>
        <FileUI pathInfo={"test/folder/text.csv"}/>
        <FileUI pathInfo={"test/folder/text.txt"}/>
        <FileUI pathInfo={"test/folder/text.txt"}/>
        <FileUI pathInfo={"test/folder/text.txt"}/>     
      </div>
    </div>
  );
};

export default FileBrowser;

 

FileUI.js

import React from "react";

import TextSnippetIcon from "@mui/icons-material/TextSnippet";
import DataObjectICON from "@mui/icons-material/DataObject";
import FolderIcon from "@mui/icons-material/Folder";

const getFileName = (path) => {
  if (path === undefined) return undefined;
  let spt = path.split("/");
  return spt[spt.length - 1];
};

const getMuiIcon = (fileName) => {
  if (fileName === undefined) return <TextSnippetIcon sx={{ fontSize: 60 }} />;

  let spt = fileName.split(".");
  if (spt.length === 1)
    return <FolderIcon sx={{ fontSize: 60, left: "50%" }} />;
  if (spt[1] === "json")
    return <DataObjectICON sx={{ fontSize: 60, left: "50%" }} />;
  return <TextSnippetIcon sx={{ fontSize: 60, left: "50%" }} />;
};

const FileUI = ({ pathInfo }) => {
  return (
    <div
      style={{
        display: "inline-block",
        width: "110px",
        height: "120px",
        backgroundColor: "silver",
        textAlign: "center",
        margin: "10px",
        cursor: "pointer",
        verticalAlign: "top",
      }}
      onClick={(e) => console.log(e)}
    >
      {getMuiIcon(getFileName(pathInfo))}
      <div style={{ width: "110px", height: "40px", wordBreak: "break-all" }}>
        <span
          style={{
            fontSize: "12px",
            backgroundColor: "green",
            cursor: "pointer",
            display: "block",
            height: "50px",
          }}
        >
          {getFileName(pathInfo)}
        </span>
      </div>
    </div>
  );
};

export default FileUI;

 

반응형

댓글