August more text challenge first, rush rush

preface

Before the HTML5 standard File API appeared, the operation of files on the front end was very limited and mostly needed to be implemented with the back end. For security reasons, when uploading a file from the local, it is impossible for the code to get the address of the file in the local user, so it is impossible for the pure front end to complete some functions like picture preview. But with the advent of the File API, it’s possible.

However, for security reasons, the File API has many limitations, such as the inability to read local files directly, the need to download files to create files, and the inability to save changes immediately after reading files.

This article introduces the File System Access API developed by [WICG(Web Incubator Community Group)](Web Incubator Community Group (WICG)). It provides a more powerful way to work with local files based on the existing FIle interface.

Description from official document:

This document defines a web platform API that enables developers to build powerful web apps that interact with files on the user’s local device. It builds on File API for file reading capabilities, and adds new API surface to enable modifying files, as well as working with directories.

This document defines a Web platform API that enables developers to build powerful Web applications that interact with files on a user’s local device. It builds on the File API, provides File reading capabilities, and adds new interfaces to support modifying files and using directories.

This interface provides a set of methods that allow web pages to directly read files and save changes locally, as well as access directories. Compared to THE File and FileReader provided by HTML5, it does provide more powerful capabilities. For now, however, the interface is only available on Chrome and Chromium’s Edge browsers.

The Web Incubator Community Group (WICG) is a division of the W3C organization responsible for designing the next generation of Web standards, so they publish interface proposals from time to time. Interested can look at this introduction WICG: Evolving the Web from the ground up | W3C Blog.

On WICG’s website, there are a number of API implementations that are being incubated. Here we focus on File System Access, which went through the initial draft, iteration, and release, and is now used.

start

We can create a new page File or use the File System Access methods directly on the console.

Open the file

To open a file, we need to call a global method called showOpenFilePicker(). This method is asynchronous and will bring up a file selection dialog box. After selecting a file, it will return an array of file handles. Including the default open file directory, file type, file number, and so on.

Bind an event to the open file button, click and call showOpenFilePicker, and save the file handle.

A file handle is usually a system identifier that can be used to describe forms, files, etc. In C++, a file handle is a pointer to all kinds of objects.

const BtnOpenFile = document.getElementById('btn-choose-file')
const editor = document.getElementById('editor')
let fileHandle;
BtnOpenFile.addEventListener('click'.async () => {
  [fileHandle] = await window.showOpenFilePicker()
});
Copy the code

From the fileHandle object, we can get information about the contents, properties, and so on of the file,

Call it getFile method, get a [Blob] (Blob () – Web API interface reference | MDN (mozilla.org)) object, then call the text () to obtain the file content, for Blob of other methods such as slice, slice is used as a common file, I won’t introduce it here.

const BtnOpenFile = document.getElementById('btn-choose-file')
const editor = document.getElementById('editor')
const fileHandle;
BtnOpenFile.addEventListener('click'.async () => {
  	[fileHandle] = await window.showOpenFilePicker()
	const fileBlob = await fileHandle.getFile()
	editor.value = await fileBlob.text()
});
Copy the code

It is also possible to open the file and get the content input tag.

The file handle obtained by getFile() may be invalidated. After the handle is acquired, deleting or moving the source file invalidates the file handle object, and you can only call getFile() again to get a new handle.

Create a new file and save it

To create a new file, we call the showSaveFilePicker() method, which opens a save file dialog box, and we can pass in parameters to set the file name, type, and so on. The call returns a handle to the newly created file.

const BtnSaveAs = document.getElementById('btn-save-as')

async function fileSaveAs(description) {
  const options = {
    types: [
      {
        description,
        accept: {
          'text/plain': ['.txt'],},},],};return await window.showSaveFilePicker(options);
}

BtnSaveAs.addEventListener('click'.async() = > {const handle = await fileSaveAs("Hello File Access Api")})Copy the code

After clicking the button and selecting the save location, the new file is created and saved, but the file is empty, and we write data to the file.

To save a file, we used to generate the tag and then simulate the click to download the file. If it is based on an existing file, it will not overwrite the original file.

Write data

To write content into the file, you need to call on the object file handle createWritable () to create flow, it is a FileSystemWritableFileStream object. Since writing files from the Web to the system is an insecure act, the browser asks us for authorization.

 async function writeFile(fileHandle, contents) {
   // createWritable() creates a WritableStream object, WritableStream
   const writable = await fileHandle.createWritable();
   // Pipe the data to the file
   await writable.write(contents);
   // The pipe needs to be closed after use
   await writable.close();
 }

BtnSaveAs.addEventListener('click'.async() = > {const handle = await fileSaveAs("Hello File Access Api")
  await writeFile(handle,editor.value)
})
Copy the code

Written to the file is the process of flow operation, using the Stream related API, use FileSystemWritableFileStream here is actually a WritableStream instance.

The write() method is responsible for writing data, which can be strings, Blob objects, or streams.

For example, the response.body of the fetch request is a readableStream instance and can therefore be directly written to the file via write() :

async function writeURLToFile(fileHandle, url) {
  / / to write FileSystemWritableFileStream instance creation
  const writable = await fileHandle.createWritable();
  // Request resources
  const response = await fetch(url);
  // Response. body is a readableStream instance that uses pipeTo to create a pipe for data transfer
  await response.body.pipeTo(writable);
  // The pipe created by pipeTo() is automatically closed
}
Copy the code

However, as with pipes in Node, if the pipe is not automatically closed after the data transfer is complete, it must be closed manually.

Use the default file name and the default directory

SuggestedName and startIn are two attributes that help you save the file. You might want to have a default filename like Typora’s Untitled filename, or you might want to set a default directory to open the file. Just drop it when you call methods like showXxxPicker.

const fileHandle = await self.showSaveFilePicker({
  // Default file name
  suggestedName: 'Untitled'.// Open the desktop directory by default
  startIn: 'desktop'
  types: [{
    description: 'Text documents'.accept: {
      'text/plain': ['.txt'],}}]});Copy the code

Here is a list of common system directories:

  • desktopDesktop:
  • documents: the document
  • downloadsDownload:
  • music: music
  • picturesImages:
  • videos: video

In addition to the usual system directory, we can also pass in handles to custom directories, which we need to get by calling showDirectoryPicker().

// Select the directory to save the file
const directoryHandle = await showDirectoryPicker()
const fileHandle = await showOpenFilePicker({
  // As the starting directory to save the file
  startIn: directoryHandle
});
Copy the code

Open the directory and get its contents

Call showDirectoryPicker (), we can choose to view the directory, and then returns a handle to the corresponding directory, it is a FileSystemDirectoryHandle object instance, the use of its values () method, we will be able to view the directory in which files.

const btnOpenDirectory = document.getElementById('btn-open-dir');
btnOpenDirectory.addEventListener('click'.async() = > {const dirHandle = await window.showDirectoryPicker();
  for await (const item of dirHandle.values()) {
    console.log(item)
  }
});
Copy the code

Before opening, the browser will ask us if we are allowed, and if we are, a directory flag will appear in the address bar.

The console outputs what files are in the directory.

However, for security reasons, the browser does not allow us to access some sensitive directories, such as directories containing system files or directories that are not allowed by group policies.

Create or access files and folders in directories

In a directory, you can call the getFileHandle() and getDirectoryHandle() methods of the directory handle to access files and subdirectories, and you can pass a {create} object argument to create a file or directory if it does not exist.

// MyDocuments will be created if it does not exist
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('MyDocuments', {
  create: true});// If text.txt does not exist in the current directory, create a new one
const newFileHandle = await newDirectoryHandle.getFileHandle('text.txt', { create: true });
Copy the code

Delete a file or directory

When you have a handle to a directory and want to delete the directory or a file under the directory, you call the removeEntry() method.

// Delete the file
await directoryHandle.removeEntry('text.txt');
Copy the code

By default, only empty directories can be deleted or an error will be reported, but you can pass in an object to recursively delete all subdirectories and files in a directory.

// Recursively delete all files and subdirectories in the directory
await directoryHandle.removeEntry('oldDir', { recursive: true });
Copy the code

Also note that if you delete an unsaved directory or file, the return result is a success.

Path of files in the parse directory

With the resolve() method of the directory handle, we get an array of the path of the file. The file must be in a directory, or subdirectory, of the directory.

const path = await directoryHandle.resolve(fileHandle);
// `path` is now ["desktop", "text.txt"]
Copy the code

Browser support

Through [Can I use](caniuse.com/?search=Fil… Access, except for Google, Edge, Opera, all other browsers do not support.

conclusion

The File System Access API is certainly more powerful than the existing File API, but because it is not a standard and will someday be deprecated or changed, it needs to be considered carefully before being used in production environments, but for individual developers, we can implement a web-side text editor. For example, a low-spec Version of Typora based on PWN, or it can be installed on the desktop as a Web application. Typora has the ability to manipulate system files based on Electron and node. The existing File API cannot open a directory. The File System Access API helps us do this. So it’s a good idea to use File System Access for a small project.

A recommended library, browser-fs-Access, provides a wrapper around the File Access System API, which is preferred when the browser supports it, and degraded when the browser does not support it.

Related Demo: browser-fs-access.js Demo

reference

  • File System Access (wicg.github.io)
  • Streams API concepts – Web API interface reference | MDN (mozilla.org)
  • File – Web API interface reference | MDN (mozilla.org)
  • WICG/file-system-access: Expose the file system on the user’s device, so Web apps can interoperate with the user’s native applications. (github.com)