본문 바로가기
개발/React

리액트 - 액션 추가하고 다크 모드 구현하기 (Add ChonkyActions for Dark Mode)

by 피로물든딸기 2024. 3. 16.
반응형

리액트 전체 링크

 

참고

- 파일 브라우저 만들기
- chonky 기본 설정 확인하기
- 액션 추가하고 다크 모드 구현하기
커스텀 액션 추가하기
- Chonky 파일 맵 만들기
- 리포지토리의 폴더 정보 저장하기
- 깃허브 리포지토리를 파일 브라우저로 불러오기
useNavigate로 Toast UI Editor 이동하기

 

현재 ChonkyBrowser.jsfileActionsCreateFolderDeleteFiles Action만 추가되어 있다.

  const fileActions = useMemo(
    () => [ChonkyActions.CreateFolder, ChonkyActions.DeleteFiles],
    []
  );

 

아래와 같이 다른 액션도 추가해 보자.

  const fileActions = useMemo(
    () => [
      ChonkyActions.CreateFolder,
      ChonkyActions.DeleteFiles,
      ChonkyActions.CopyFiles,
      ChonkyActions.UploadFiles,
      ChonkyActions.DownloadFiles,
      ChonkyActions.ToggleDarkMode,
    ],
    []
  );

CopyFiles

 

 

복사 할 수 있는 메뉴가 추가된다. 


UploadFiles

 

업로드를 할 수 있는 버튼이 추가된다.


DownloadFiles

 

파일을 다운로드 할 수 있는 메뉴가 추가된다.


ToggleDarkMode

 

토글 버튼이 추가된다.


DarkMode 토글 버튼 구현

 

위의 Action들은 추가를 해도 기능은 직접 구현해야 한다. 

 

여기서 다크 모드만 구현해 보자.

  const fileActions = useMemo(
    () => [
      ChonkyActions.CreateFolder,
      ChonkyActions.DeleteFiles,
      // ChonkyActions.CopyFiles,
      // ChonkyActions.UploadFiles,
      // ChonkyActions.DownloadFiles,
      ChonkyActions.ToggleDarkMode,
    ],
    []
  );

 

darkModeon / off 하기 위해 useState로 변수를 추가한다.

그리고 toggle 메서드를 만들자.

const ChonkyBrowser = React.memo((props) => {
  const [darkMode, setDarkMode] = useState(false);
  const toggleDarkMode = () => {
    setDarkMode(!darkMode);
  }
  
  ...

 

FileBrowser 컴포넌트에 darkMode를 추가한다.

  <FullFileBrowser
    files={files}
    folderChain={folderChain}
    ...
    darkMode={darkMode}
    {...props}
  />

 

handleFileActiontoggleDarkMode를 추가한다.

  const handleFileAction = useFileActionHandler(
    setCurrentFolderId,
    deleteFiles,
    moveFiles,
    createFolder,
    toggleDarkMode
  );

 

useFileActionHandler에도 toggleDarkMode를 추가하고 구체적인 구현을 한다.

다크 모드이미 구현되어 있으므로 useState로 선언된 darkModetoggle하면 된다.

const useFileActionHandler = (
  setCurrentFolderId,
  deleteFiles,
  moveFiles,
  createFolder,
  toggleDarkMode // 추가
) => {
  return useCallback(
    (data) => {
    
      ...
      
      // 추가
      else if (data.id === ChonkyActions.ToggleDarkMode.id) {
        toggleDarkMode();
      }
    },
    [createFolder, deleteFiles, moveFiles, setCurrentFolderId, toggleDarkMode] // 추가
  );
};

 

이제 토글 버튼을 클릭해 보자.

 

전체 코드는 다음과 같다.

import React, { useState, useCallback, useEffect, useRef, useMemo} from "react";
import { setChonkyDefaults, ChonkyActions, FileHelper, FullFileBrowser} from "chonky";
import { ChonkyIconFA } from "chonky-icon-fontawesome";

import Box from "@mui/material/Box";

const demoMap = require("./demo.json");

const prepareCustomFileMap = () => {
  const baseFileMap = demoMap.fileMap;
  const rootFolderId = demoMap.rootFolderId;
  return { baseFileMap, rootFolderId };
};

const useCustomFileMap = () => {
  const { baseFileMap, rootFolderId } = useMemo(prepareCustomFileMap, []);

  const [fileMap, setFileMap] = useState(baseFileMap);
  const [currentFolderId, setCurrentFolderId] = useState(rootFolderId);

  const resetFileMap = useCallback(() => {
    setFileMap(baseFileMap);
    setCurrentFolderId(rootFolderId);
  }, [baseFileMap, rootFolderId]);

  const currentFolderIdRef = useRef(currentFolderId);
  useEffect(() => {
    currentFolderIdRef.current = currentFolderId;
  }, [currentFolderId]);

  const deleteFiles = useCallback((files) => {
    setFileMap((currentFileMap) => {
      const newFileMap = { ...currentFileMap };
      files.forEach((file) => {
        delete newFileMap[file.id];

        if (file.parentId) {
          const parent = newFileMap[file.parentId];
          const newChildrenIds = parent.childrenIds.filter(
            (id) => id !== file.id
          );
          newFileMap[file.parentId] = {
            ...parent,
            childrenIds: newChildrenIds,
            childrenCount: newChildrenIds.length,
          };
        }
      });

      return newFileMap;
    });
  }, []);

  const moveFiles = useCallback((files, source, destination) => {
    setFileMap((currentFileMap) => {
      const newFileMap = { ...currentFileMap };
      const moveFileIds = new Set(files.map((f) => f.id));

      const newSourceChildrenIds = source.childrenIds.filter(
        (id) => !moveFileIds.has(id)
      );
      newFileMap[source.id] = {
        ...source,
        childrenIds: newSourceChildrenIds,
        childrenCount: newSourceChildrenIds.length,
      };

      const newDestinationChildrenIds = [
        ...destination.childrenIds,
        ...files.map((f) => f.id),
      ];
      newFileMap[destination.id] = {
        ...destination,
        childrenIds: newDestinationChildrenIds,
        childrenCount: newDestinationChildrenIds.length,
      };

      files.forEach((file) => {
        newFileMap[file.id] = {
          ...file,
          parentId: destination.id,
        };
      });

      return newFileMap;
    });
  }, []);

  const idCounter = useRef(0);
  const createFolder = useCallback((folderName) => {
    setFileMap((currentFileMap) => {
      const newFileMap = { ...currentFileMap };

      const newFolderId = `new-folder-${idCounter.current++}`;
      newFileMap[newFolderId] = {
        id: newFolderId,
        name: folderName,
        isDir: true,
        modDate: new Date(),
        parentId: currentFolderIdRef.current,
        childrenIds: [],
        childrenCount: 0,
      };

      const parent = newFileMap[currentFolderIdRef.current];
      newFileMap[currentFolderIdRef.current] = {
        ...parent,
        childrenIds: [...parent.childrenIds, newFolderId],
      };

      return newFileMap;
    });
  }, []);

  return {
    fileMap,
    currentFolderId,
    setCurrentFolderId,
    resetFileMap,
    deleteFiles,
    moveFiles,
    createFolder,
  };
};

const useFiles = (fileMap, currentFolderId) => {
  return useMemo(() => {
    const currentFolder = fileMap[currentFolderId];
    const childrenIds = currentFolder.childrenIds;
    const files = childrenIds.map((fileId) => fileMap[fileId]);
    return files;
  }, [currentFolderId, fileMap]);
};

const useFolderChain = (fileMap, currentFolderId) => {
  return useMemo(() => {
    const currentFolder = fileMap[currentFolderId];
    const folderChain = [currentFolder];
    let parentId = currentFolder.parentId;
    while (parentId) {
      const parentFile = fileMap[parentId];
      if (parentFile) {
        folderChain.unshift(parentFile);
        parentId = parentFile.parentId;
      } else {
        break;
      }
    }
    return folderChain;
  }, [currentFolderId, fileMap]);
};

const useFileActionHandler = (
  setCurrentFolderId,
  deleteFiles,
  moveFiles,
  createFolder,
  toggleDarkMode
) => {
  return useCallback(
    (data) => {
      if (data.id === ChonkyActions.OpenFiles.id) {
        const { targetFile, files } = data.payload;
        const fileToOpen = targetFile || files[0];
        if (fileToOpen && FileHelper.isDirectory(fileToOpen)) {
          setCurrentFolderId(fileToOpen.id);
          return;
        }
      } else if (data.id === ChonkyActions.DeleteFiles.id) {
        deleteFiles(data.state.selectedFilesForAction);
      } else if (data.id === ChonkyActions.MoveFiles.id) {
        moveFiles(
          data.payload.files,
          data.payload.source,
          data.payload.destination
        );
      } else if (data.id === ChonkyActions.CreateFolder.id) {
        const folderName = prompt("Provide the name for your new folder:");
        if (folderName) createFolder(folderName);
      } else if (data.id === ChonkyActions.ToggleDarkMode.id) {
        toggleDarkMode();
      }

      console.log(data);
      //showActionNotification(data);
    },
    [createFolder, deleteFiles, moveFiles, setCurrentFolderId, toggleDarkMode]
  );
};

const ChonkyBrowser = React.memo((props) => {
  const [darkMode, setDarkMode] = useState(false);
  const toggleDarkMode = () => {
    setDarkMode(!darkMode);
  }

  const {
    fileMap,
    currentFolderId,
    setCurrentFolderId,
    deleteFiles,
    moveFiles,
    createFolder,
  } = useCustomFileMap();

  setChonkyDefaults({ iconComponent: ChonkyIconFA });
  
  const files = useFiles(fileMap, currentFolderId);
  const folderChain = useFolderChain(fileMap, currentFolderId);
  const handleFileAction = useFileActionHandler(
    setCurrentFolderId,
    deleteFiles,
    moveFiles,
    createFolder,
    toggleDarkMode
  );

  const fileActions = useMemo(
    () => [
      ChonkyActions.CreateFolder,
      ChonkyActions.DeleteFiles,
      // ChonkyActions.CopyFiles,
      // ChonkyActions.UploadFiles,
      // ChonkyActions.DownloadFiles,
      ChonkyActions.ToggleDarkMode,
    ],
    []
  );

  const thumbnailGenerator = useCallback(
    (file) =>
      file.thumbnailUrl ? `https://chonky.io${file.thumbnailUrl}` : null,
    []
  );

  return (
    <Box sx={{ m: 2 }}>
      <div style={{ height: 400 }}>
        <FullFileBrowser
          files={files}
          folderChain={folderChain}
          fileActions={fileActions} 
          onFileAction={handleFileAction}
          thumbnailGenerator={thumbnailGenerator}
          // disableDefaultFileActions={true} // default false
          // doubleClickDelay={500} // ms
          // disableSelection={true} // default false 파일 선택이 해제됨
          // disableDragAndDrop={true} // 드래그 앤 드랍 기능 off
          // disableDragAndDropProvider={true} // default false, Provider : 다른 드래그 앤 드롭은 유지
          // defaultSortActionId={ChonkyActions.SortFilesByDate.id} // SortFilesByName, SortFilesBySize, SortFilesByDate
          // defaultFileViewActionId={ChonkyActions.EnableListView.id} // EnableGridView, EnableListView  
          // clearSelectionOnOutsideClick={false} // default true 브라우저 외부 클릭 시 파일 선택 해제
          darkMode={darkMode}
          {...props}
        />
      </div>
    </Box>
  );
});

export default ChonkyBrowser;
반응형

댓글