Przeglądaj pliki na dysku z poziomu Web z File System Access API

Spis treści

  1. Wprowadzenie
  2. File System Access API
    1. Krótkie wtrącenie o Origin Private File System
    2. Stworzenie prostej przeglądarki plików tekstowych
      1. Założenia
      2. Implementacja
  3. Działająca aplikacja na Github Pages
  4. Pełen kod aplikacji na Github
  5. Podsumowanie

Wprowadzenie

Ukazał się już na portalu post o File System Access API:

I w momencie pisania tamtego artykułu wydawało się, że zostanie ono raczej tylko ciekawostką zaimplementowaną tylko w Chrome.

W lutym 2022 tego roku okazało się, że ekosystem Apple również dostanie to API i będzie w nim wspierane. W mocno ograniczonej formie, jako Origin Private File System – ale jednak.

Warto więc odświeżyć temat, pokazując jak napisać mega uproszczoną przeglądarkę plików, która pozwoli otworzyć folder oraz wyświetlić zagnieżdżone w nim foldery oraz pliki.

Zapraszam do czytania 🙂

File System Access API

Krótkie wtrącenie o Origin Private File System

OPFS różni się od implementacji desktopowej. Wersja desktopowa pozwala czytać rzeczywiste pliki i foldery na dysku twardym użytkownika komputera – stąd straszne ostrzeżenie w momencie gdy aplikacja pyta o uprawienia 🙂

OPFS działa w nieco inny sposób – mamy możliwość tworzenia i odczytywania plików i katalogów w ramach jednej domeny i nie ma gwarancji, w jaki sposób przeglądarka będzie nasze pliki reprezentować – mogą być one “bezpośrednio” na dysku bądź też w bazie danych.

Przykładowo – plików utworzonych w ramach domeny https://www.wp.pl/ nie da się odczytać w ramach https://frontcave.pl/

Jeśli chcesz sprawdzić jak działa OPFS, możesz wejść np z iPhone na przykład implementacji:

https://radek-anuszewski.github.io/file-system-acces-api-ios-mac/

Link był fragmentem newslettera który wysyłam co dwa tygodnie liście mailowej, na którą możesz zapisać się tutaj (dostaniesz też ciekawy dokument dotyczący Reacta):

Stworzenie prostej przeglądarki plików tekstowych

Założenia

Przeglądarka wczyta pliki tekstowe i foldery z wybranego katalogu oraz umożliwi wyświetlenie zawartości plików tekstowych po kliknięciu.

Więc implementacja będzie zawierać:

  1. Wczytanie zagnieżdżonych plików i folderów X poziomów w dół
  2. Odfiltrowanie plików innych niż z rozszerzeniem txt
  3. Wyświetlanie zawartości pliku po kliknięciu

Implementacja

Akcję otwarcia pickera z folderami podepniemy pod przycisk:

document.querySelector('#selectMain').addEventListener('click', async () => {
  data = {};
  const mainDirectoryHandle = await window.showDirectoryPicker();
  await readElements(mainDirectoryHandle, data, 0);
  drawElements(data);
});

Funkcja showDirectoryPicker spowoduje otwarcie pickera z możliwością wyboru katalogu.

Zwrócony zostanie handler typu FileSystemDirectoryHandle, który w metodzie entries zwróci nam handlery do zawartych w nim folderów i plików.

Skorzysta z tej metody funkcja readElements:

const readElements = async (handle, elData, level, maxLevel = 5) => {
  if (level >= maxLevel) {
    return;
  }
  for await (const [elName, elHandle] of handle.entries()) {
    if (elHandle.kind === 'directory' || elHandle.name.endsWith('.txt')) {
      elData[elName] = {
        name: elName,
        kind: elHandle.kind,
        handle: elHandle,
        ...(elHandle.kind === 'directory' ? {data: {}} : {}),
        ...(elHandle.kind === 'file' ? {file: await elHandle.getFile()} : {}),
      }

      if (elHandle.kind === 'directory') {
        await readElements(elHandle, elData[elName].data, level + 1);
      }
    }
  }
}

Wewnątrz funkcji odczytujemy zawartość handlera i wpisujemy ją do obiektu – jeśli trafimy na folder tworzymy nowy zagnieżdżony obiekt, do którego rekurencyjnie dopiszemy zawartość tego folderu – parametr maxLevel o wartości 5 oznacza, że zejdziemy maksymalnie 5 poziomów w dół.

Jeśli trafimy na plik, od razu pobierzemy go za pomocą metody getFile.

Jak pewnie udało Ci się zauważyć, za pomocą ifa odfiltrowywujemy wszystko, co nie jest katalogiem bądź plikiem z rozszerzeniem txt. W dalszej części artykułu znajdziesz link do działającej aplikacji w Github Pages – najlepiej stwórz sobie zagnieżdżoną strukturę folderów z plikami txt, by lepiej móc zobaczyć działanie aplikacji.

W momencie gdy mamy już obiekt z danymi, na jego podstawie możemy stworzyć DOM naszej przeglądarki plików za pomocą metody drawElements:

const drawElements = (elData, indentText = '') => {
  for (const key in elData) {
    if (elData.hasOwnProperty(key)) {
      const el = elData[key];
      const directory = el.kind === 'directory';
      const div = document.createElement('div');
      div.innerHTML = `${indentText}${directory ? `<strong>${el.name}</strong>` : el.name}`;
      div.style.padding = '5px 0';
      if (el.kind === 'file') {
        div.style.cursor = 'pointer';
        div.onclick = async () => document.querySelector('#fileText').innerHTML = await el.file.text();
      }
      document.querySelector('#elements').appendChild(div);
      if (directory) {
        drawElements(el.data, indentText + '--');
      }
    }
  }
};

Struktura HTML jest płaska, zagnieżdżenie folderów pokazywane jest za pomocą ilości znaków ==. Jeżeli natrafiamy na plik, podpinamy pod renderowany element metodę, która z pliku za pomocą metody text wyciąga zawartość tekstową którą następnie można wyświetlić. Foldery odróżniają się od plików tym, że są pogrubione:

Działająca aplikacja na Github Pages

Działającą aplikację znajdziesz pod adresem:

https://radek-anuszewski.github.io/simple-file-browser/

Dobrze testować ją na zagnieżdżonych folderach z kilkoma plikami tekstowymi, wtedy najlepiej widać że możemy wczytywać pliki i foldery niżej głębiej niż tylko w folderze głównym.

Pełen kod aplikacji na Github

https://github.com/radek-anuszewski/simple-file-browser

Podsumowanie

Choć wsparcie tylko dla OPFS w ekosystemie Apple nie wydaje się być mocno użyteczne, pamiętajmy że to kolejny krok w rozszerzeniu wsparcia. Stąd warto pamiętać o File System Access API i co jakiś czas odświeżać wiedzę o nim, ponieważ widać że twórcy przeglądarek coraz bardziej się do niego przekonują.