React 파일 관리자를 만들어 봅시다 14장: 파일 관리자 본문

그래서 사이드바와 로더의 이전 장에서 많은 작업을 수행했으며 이제 파일 관리자 본체에 약간의 노력을 기울일 때입니다.

작은 업데이트 진행 중 바 로더



진행률이 0 인 경우 흰색 배경만 표시합니다.

// LoadingProgressBar.tsx
...
  return (
    <Progress
      size="lg"
      value={progress}
      striped
    // 👇🏻  We'll add the styles prop to style the root
      styles={{
        root: {
          backgroundColor: progress === 0 ? "white" : undefined,
        },
      }}
      label={progress > 0 ? `${progress}%` : undefined}
      color={mapProgressColor()}
      animate
    />
  );


자체 구성 요소에서 오버레이 분리



이제 알다시피 오버레이는 파일 관리자 로딩 상태에 나타나므로 로딩이 발생할 때만 렌더링되는 별도의 구성 요소를 생성합니다.

새 파일Content/ContentOverlay.tsx을 만들고 다음 코드를 추가해 보겠습니다.

// ContentOverlay.tsx
import { LoadingOverlay } from "@mantine/core";
import { useLoading } from "app/file-manager/hooks";

export default function ContentOverlay() {
  const isLoading = useLoading();
  return <LoadingOverlay visible={isLoading} overlayBlur={2} />;
}


이제 Content.tsx로 가져오겠습니다.

// Content.tsx
import { Card } from "@mantine/core";
import { ContentWrapper } from "./Content.styles";
import ContentOverlay from "./ContentOverlay"; 👈🏻

export default function Content() {
  return (
    <>
      <Card shadow="sm">
        <ContentWrapper>
          <ContentOverlay /> 👈🏻
        </ContentWrapper>
      </Card>
    </>
  );
}


노드 목록



이제 큰 부분인 노드 목록에 대한 시간이므로 모든 노드를 렌더링할 새 파일Content/NodesList.tsx을 생성하겠습니다.

그러나 우리는 노드를 두 개의 배열로 분할해야 합니다. 하나는 폴더용이고 다른 하나는 파일용입니다.

// NodesList.tsx
import { Grid } from "@mantine/core";
import { useKernel } from "app/file-manager/hooks";
import { useMemo } from "react";
import { DirectoryNode, FileNode } from "./ContentNode";

export default function NodesList() {
  const kernel = useKernel();

  const currentDirectoryNode = kernel.currentDirectoryNode;

  const [directories, files] = useMemo(() => {
    const node = currentDirectoryNode;

    if (!node || !node.children?.length) return [[], []];

    return [
      node.children.filter(node => node.isDirectory),
      node.children.filter(node => !node.isDirectory),
    ];
  }, [currentDirectoryNode]);

  return (
    <>
      <Grid>
        {directories.map(node => (
          <Grid.Col key={node.path} span={2}>
          {node.name}
          </Grid.Col>
        ))}
        {files.map(node => (
          <Grid.Col key={node.path} span={2}>
          {node.name}
          </Grid.Col>
        ))}
      </Grid>
    </>
  );
}


우리는 모든 디렉터리 노드와 파일 노드를 나열하는 메모를 만들었지만 먼저 로드할 현재 디렉터리 노드가 있고 자식도 있는지 확인하려고 합니다.

이제 다음과 같은 내용이 표시됩니다.



디렉토리 노드 및 파일 노드



따라서 디렉터리 노드 목록 또는 파일 노드 목록에서 각 노드를 완전히 제어해야 하므로 두 구성 요소DirectoryNodeFileNode를 생성하여 각 노드를 처리하고 Content/ContentNode에 생성됩니다. ) 디렉토리.

// Content/ContentNode/DirectoryNode.tsx

import { DirectoryNodeProps } from "./ContentNode.types";

export default function DirectoryNode({ node }: DirectoryNodeProps) {
    return <>{node.name}</>;
}



// Content/ContentNode/FileNode.tsx

import { FileNodeProps } from "./ContentNode.types";

export default function FileNode({ node }: FileNodeProps) {
    return <>{node.name}</>;
}


두 구성 요소의 유형을 보유하도록 ContentNode.types.ts를 생성해 보겠습니다.

// Content/ContentNode/ContentNode.types.ts
import { Node } from "app/file-manager/Kernel";

export type FileNodeProps = {
  node: Node;
};

export type DirectoryNodeProps = {
  node: Node;
};


props에 추가한 모든 것은 노드 객체입니다.

이제 디렉터리의 모든 구성 요소를 내보내는 인덱스 파일을 만들어 보겠습니다.

// Content/ContentNode/index.ts

export { default as DirectoryNode } from "./DirectoryNode";
export { default as FileNode } from "./FileNode";


이제 NodesList에서 이 노드를 가져오겠습니다.

// NodesList.tsx
import { Grid } from "@mantine/core";
import { useKernel } from "app/file-manager/hooks";
import { useMemo } from "react";
👉🏻 import { DirectoryNode, FileNode } from "./ContentNode";

export default function NodesList() {
  const kernel = useKernel();

  const currentDirectoryNode = kernel.currentDirectoryNode;

  const [directories, files] = useMemo(() => {
    const node = currentDirectoryNode;

    if (!node || !node.children?.length) return [[], []];

    return [
      node.children.filter(node => node.isDirectory),
      node.children.filter(node => !node.isDirectory),
    ];
  }, [currentDirectoryNode]);

  return (
    <>
      <Grid>
        {directories.map(node => (
          <Grid.Col key={node.path} span={2}>
👉🏻            <DirectoryNode node={node} />
          </Grid.Col>
        ))}
        {files.map(node => (
          <Grid.Col key={node.path} span={2}>
👉🏻            <FileNode node={node} />
          </Grid.Col>
        ))}
      </Grid>
    </>
  );
}


useCurrentDirectoryNode 후크



현재 디렉터리 노드를 반환하는 새 후크를 생성하고 변경 사항을 수신합니다. 그렇다면 이 후크를 사용하는 구성 요소를 다시 렌더링합니다.

// hooks/useCurrentDirectoryNode.ts

import { Node } from "app/file-manager/Kernel";
import { useEffect, useState } from "react";
import useKernel from "./useKernel";

export default function useCurrentDirectoryNode() {
  const kernel = useKernel();
  const [node, setNode] = useState<Node | undefined>(
    kernel.currentDirectoryNode,
  );

  useEffect(() => {
    const event = kernel.on("directoryChange", setNode);

    return () => event.unsubscribe();
  }, [kernel]);

  return node;
}


우리는 단순히 커널에서 현재 디렉토리 노드를 가져와 state에 설정한 다음 현재 노드 변경에 발생하는 모든 변경 사항을 관찰했습니다. 이 경우 상태를 업데이트하여 구성 요소가 다시 렌더링됩니다.

useEvent 사용



이벤트useEvent를 처리할 이벤트unsubscribe와 함께 사용할 수 있는 또 다른 좋은 후크가 있으므로 걱정할 필요가 없습니다.

// hooks/useCurrentDirectoryNode.ts

👉🏻 import { useEvent } from "@mongez/react";
import { Node } from "app/file-manager/Kernel";
import { useState } from "react";
import useKernel from "./useKernel";

export default function useCurrentDirectoryNode() {
  const kernel = useKernel();
  const [node, setNode] = useState<Node | undefined>(
    kernel.currentDirectoryNode,
  );

👉🏻   useEvent(() => kernel.on("directoryChange", setNode));

  return node;
}


이것은 이전 코드와 정확히 동일한 효과를 수행하지만 더 읽기 쉽고 구독 취소 이벤트에 대해 걱정할 필요가 없습니다.

Note that the useEvent callback must return the Event Subscription.



이제 현재 디렉터리 노드의 변경 사항을 수신하도록 NodesList를 업데이트하여 새 렌더링을 만듭니다.

// NodesList.tsx
import { Grid } from "@mantine/core";
// 👇🏻 import the hook
import { useCurrentDirectoryNode } from "app/file-manager/hooks";
import { useMemo } from "react";
import { DirectoryNode, FileNode } from "./ContentNode";

export default function NodesList() {
   // 👇🏻 remove the useKernel hook and replace it with useCurrentDirectoryNode

   const kernel = useKernel();

   const currentDirectoryNode = kernel.currentDirectoryNode;

   const currentDirectoryNode = useCurrentDirectoryNode();

  const [directories, files] = useMemo(() => {
    const node = currentDirectoryNode;

    if (!node || !node.children?.length) return [[], []];

    return [
      node.children.filter(node => node.isDirectory),
      node.children.filter(node => !node.isDirectory),
    ];
  }, [currentDirectoryNode]);

  return (
    <>
      <Grid>
        {directories.map(node => (
          <Grid.Col key={node.path} span={2}>
            <DirectoryNode node={node} />
          </Grid.Col>
        ))}
        {files.map(node => (
          <Grid.Col key={node.path} span={2}>
            <FileNode node={node} />
          </Grid.Col>
        ))}
      </Grid>
    </>
  );
}


DirectoryNode 및 FileNode의 UI 개선



이제 각 노드에 대한 멋진 UI를 만들어 보겠습니다. Mantine의 NavLink 구성 요소와 일부 아이콘도 사용할 것입니다.

// DirectoryNode.tsx
import { NavLink, useMantineTheme } from "@mantine/core";
import { IconFolder } from "@tabler/icons";
import { useKernel } from "app/file-manager/hooks";
import { DirectoryNodeProps } from "./ContentNode.types";

export default function DirectoryNode({ node }: DirectoryNodeProps) {
    // 👇🏻 get the theme
  const theme = useMantineTheme();
  const kernel = useKernel();

  return (
    // 👇🏻 use NavLink component
    <NavLink
      style={{
        // 👇🏻 center the node and make the cursor to be default
        textAlign: "center",
        cursor: "default",
      }}
      // 👇🏻 add the icon, we'll use the IconFolder and give it some good styles from the theme
      label={
        <>        
          <IconFolder
            fill={theme.colors.blue[4]}
            strokeWidth={1.5}
            color={theme.colors.blue[9]}
            size={40}
          />
          <div>{node.name}</div>
        </>
      }
    />
  );
}

FileNode 구성 요소에서 동일한 작업을 수행해 보겠습니다.

// FileNode.tsx
import { NavLink, useMantineTheme } from "@mantine/core";
import { IconFileInfo as Icon } from "@tabler/icons";
import { FileNodeProps } from "./ContentNode.types";

export default function FileNode({ node }: FileNodeProps) {
  const theme = useMantineTheme();
  return (
    <NavLink
      style={{
        textAlign: "center",
        cursor: "default",
      }}
      label={
        <>
          <Icon
            fill={theme.colors.green[4]}
            strokeWidth={1.5}
            color={theme.colors.green[9]}
            size={40}
          />
          <div>{node.name}</div>
        </>
      }
    />
  );
}


이제 최종 UI는 다음과 같습니다.



더블 클릭 시 디렉토리 열기



약간의 기능을 추가해 보겠습니다. 사용자가 디렉터리 노드를 두 번 클릭하면 열 수 있습니다.

// DirectoryNode.tsx
import { NavLink, useMantineTheme } from "@mantine/core";
import { IconFolder } from "@tabler/icons";
import { useKernel } from "app/file-manager/hooks";
import { DirectoryNodeProps } from "./ContentNode.types";

export default function DirectoryNode({ node }: DirectoryNodeProps) {
  const theme = useMantineTheme();
  const kernel = useKernel();

  return (
    <NavLink
      style={{
        textAlign: "center",
        cursor: "default",
      }}
      // 👇🏻 add the onDoubleClick event
      onDoubleClick={() => kernel.load(node.path)}
      label={
        <>
          <IconFolder
            fill={theme.colors.blue[4]}
            strokeWidth={1.5}
            color={theme.colors.blue[9]}
            size={40}
          />
          <div>{node.name}</div>
        </>
      }
    />
  );
}


기사 저장소



Github Repository에서 챕터 파일을 볼 수 있습니다.

Don't forget the main branch has the latest updated code.



지금 어디 있는지 말해줘



이 시리즈를 저와 함께 후속 조치하는 경우 현재 위치와 어려움을 겪고 있는 부분을 알려주시면 최대한 도와드리겠습니다.

살람.

좋은 웹페이지 즐겨찾기