5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クソアプリAdvent Calendar 2024

Day 2

ブラウザでエクスプローラーっぽくフォルダ構造を表示するツール

Posted at

はじめに

ブラウザ上でフォルダ情報を受け取って

エクスプローラーっぽくフォルダ表示したくなったので実装してみる。

(ただ、ファイル情報を階層とアイコンで表示するだけの機能。。。🤔)

対象

  • フォルダがUploadできる端末(directory属性が有効であること)

今回作る成果物

こんな感じのアプリを作成する。

主な機能は

フォルダUpload

  • 画面

ブラウザでエクスプローラーっぽくフォルダ構造を表示する_01.jpg

フォルダ表示(Close)

ブラウザでエクスプローラーっぽくフォルダ構造を表示する_02.jpg

フォルダ表示(Open)

ブラウザでエクスプローラーっぽくフォルダ構造を表示する_03.jpg

多重フォルダ表示(Open)

ブラウザでエクスプローラーっぽくフォルダ構造を表示する_04.jpg

プロジェクトの作成

今回はReactで実装していく。

Folderを表示するコンポーネントとFileを表示するコンポーネントを作成する。

extensionSVGMapを作ってPublic内のSVGIconと拡張子を紐づけて表示する。

(
SVGIconは
https://github.com/vscode-icons/vscode-icons
を使用する。
)

App.jsx

//略

  const MyFolder = ({ children, title }) => {
    const [isOpen, setIsOpen] = useState(false);
    return (
      <details onToggle={(e) => setIsOpen(e.target.open)}>
        <summary className='filebase'><img className='fileicon' src={isOpen ? './default_folder_opened.svg' : './default_folder.svg'} alt='folder' />{title}</summary>
        <div style={{ paddingLeft: '1em' }}>{children}</div>
      </details>
    );
  };

  const MyFile = ({ title }) => {
    const ext = title.split('.').pop();
    const extSVGname = extensionSVGMap[ext.toLowerCase()] || ext;
    const svgPath = `./file_type_${extSVGname}.svg`;
    return <div className='filebase'>  <img className='fileicon' src={svgPath} alt={ext} onError={(e) => { e.target.src = './default_file.svg'; }} />{title}</div>;
  };

//略

extensionSVGMap.js
export const extensionSVGMap = {
  'jsx': 'reactjs',
  'tsx': 'reactts',
  'ts': 'typescript',
  'rs': 'rust',
  'rb': 'ruby',
  'py': 'python',
  'cs': 'csharp',
  'env': 'dotenv',
  'xlsx': 'excel',
  //...略
};

files が存在しない場合は、

"Folderを指定してください" というメッセージを表示する。

files が存在する場合は、

ツリーオブジェクトを再帰的に探索し、MyFile コンポーネントと MyFolder コンポーネントを使って描画する。

renderNode 関数でnode と name を引数として受け取ります。

node が文字列の場合、MyFile コンポーネントを返す。

node がオブジェクトの場合、MyFolder コンポーネントを返す。

(Object.entries でループし、renderNode 関数を再帰的に呼び出し、結果を MyFolder コンポーネントの子要素として追加する。)

App.jsx

//略

  const renderTree = (files) => {
    if (!files) return <div>Folderを指定してください</div>;

    const tree = {};

    for (const file of files) {
      const pathParts = file.webkitRelativePath.split('/');
      let currentLevel = tree;

      for (let i = 0; i < pathParts.length - 1; i++) {
        const part = pathParts[i];
        if (!currentLevel[part]) {
          currentLevel[part] = {};
        }
        currentLevel = currentLevel[part];
      }
      currentLevel[pathParts.slice(-1)[0]] = file.name; 
    }

    const renderNode = (node, name) => {
      if (typeof node === 'string') {
        return <MyFile key={node} title={node} />;
      }
      return (
        <MyFolder key={name} title={name}>
          {Object.entries(node).map(([key, value]) => renderNode(value, key))}
        </MyFolder>
      );
    };

    return Object.entries(tree).map(([key, value]) => renderNode(value, key));
  };

//略

完成!

今回の成果物_demoURL/ソース

デモURL

ソース

まとめ

今回はブラウザでエクスプローラーっぽいフォルダ構造を表示してみたが、

ブラウザ上で、視覚情報としてフォルダ表示フォルダ展開したいときなどには役に立ちそう。💪

Uploadでdirectory属性を使うことで

フォルダー単位で扱えることを知らなかったので、良い経験になった。😊

5
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?