From 2a0e2830634ac450add79299563c51eb78d108e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:20:52 +0000 Subject: [PATCH 1/2] Initial plan From 5fe3905b913b4219739d6b9be8b381c2801b81e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:40:42 +0000 Subject: [PATCH 2/2] Changes before error encountered Co-authored-by: TimMikeladze <702718+TimMikeladze@users.noreply.github.com> --- README.md | 396 +- package-lock.json | 14942 ++++++++++++++++++++++++++++++++++++++++++ package.json | 7 +- src/index.ts | 372 ++ src/useOPFS.test.ts | 276 + 5 files changed, 15986 insertions(+), 7 deletions(-) create mode 100644 package-lock.json create mode 100644 src/useOPFS.test.ts diff --git a/README.md b/README.md index 65150b8..738332a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,27 @@ # 🗂️ use-fs -A React hook for integrating with the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API). Visit [**use-fs.com**](https://use-fs.com) to try it out in your browser. +A React hook for integrating with the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API) and [Origin Private File System (OPFS)](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API). Visit [**use-fs.com**](https://use-fs.com) to try it out in your browser. -The File System Access API enables web applications to seamlessly work with files on a user's local system. After a user grants permission, web apps can read, write, and manage files directly - eliminating the need for repeated file selection dialogs. This capability is ideal for creating powerful browser-based tools. +This library provides two main hooks: -Unlike traditional file selection dialogs, the user will be prompted to select a directory, the hook will watch the files in that directory for changes - rerendering when changes are detected. +1. **`useFs`** - For working with the user's local file system via the File System Access API +2. **`useOPFS`** - For working with the Origin Private File System (OPFS) -> ⚠️ Note: The File System API is not supported in all browsers. Works on Desktop in Chrome, Edge and Opera. +## 🔄 File System Access API vs OPFS + +| Feature | File System Access API (`useFs`) | OPFS (`useOPFS`) | +|---------|------------------------------|------------------| +| **User Permission** | Requires user permission prompt | No permission required | +| **File Location** | User's local file system | Private to your web app | +| **Persistence** | Files remain on user's device | Files persist in browser storage | +| **Browser Support** | Chrome, Edge, Opera (desktop) | Chrome, Firefox, Safari (modern) | +| **Use Case** | User file editing, document apps | App data, caching, temporary files | + +The File System Access API enables web applications to seamlessly work with files on a user's local system. After a user grants permission, web apps can read, write, and manage files directly - eliminating the need for repeated file selection dialogs. + +OPFS provides a private file system for web applications that persists across sessions without requiring user permission. It's ideal for storing application data, caches, and temporary files. + +> ⚠️ Note: Browser support varies. `useFs` works on Desktop in Chrome, Edge and Opera. `useOPFS` works in modern browsers including Chrome, Firefox, and Safari. ## 📡 Install @@ -22,6 +37,10 @@ pnpm add use-fs ## 🚀 Getting Started +### Using the File System Access API (`useFs`) + +The `useFs` hook provides access to the user's local file system with permission prompts and directory watching capabilities. + ```tsx import { useFs } from "use-fs"; @@ -149,6 +168,108 @@ function App() { } ``` +### Using the Origin Private File System (`useOPFS`) + +The `useOPFS` hook provides access to a private file system for your web application without requiring user permissions. + +```tsx +import { useOPFS } from "use-fs"; + +function OPFSApp() { + const { + isSupported, + readFile, + writeFile, + deleteFile, + createDirectory, + deleteDirectory, + listFiles, + exists, + getFileInfo + } = useOPFS({ + onError: (error) => { + console.error('OPFS Error:', error); + } + }); + + if (!isSupported) { + return
OPFS not supported in this browser
; + } + + const handleCreateFile = async () => { + try { + await writeFile('documents/notes.txt', 'Hello, OPFS!'); + console.log('File created successfully'); + } catch (error) { + console.error('Error creating file:', error); + } + }; + + const handleReadFile = async () => { + try { + const content = await readFile('documents/notes.txt'); + console.log('File content:', content); + } catch (error) { + console.error('Error reading file:', error); + } + }; + + const handleListFiles = async () => { + try { + const files = await listFiles('documents'); + console.log('Files in documents:', files); + } catch (error) { + console.error('Error listing files:', error); + } + }; + + const handleCreateDirectory = async () => { + try { + await createDirectory('documents/photos'); + console.log('Directory created successfully'); + } catch (error) { + console.error('Error creating directory:', error); + } + }; + + const handleCheckExists = async () => { + try { + const fileExists = await exists('documents/notes.txt'); + console.log('File exists:', fileExists); + } catch (error) { + console.error('Error checking file existence:', error); + } + }; + + const handleGetFileInfo = async () => { + try { + const info = await getFileInfo('documents/notes.txt'); + console.log('File info:', info); + } catch (error) { + console.error('Error getting file info:', error); + } + }; + + return ( +
+

OPFS File Operations

+ + + + + + + + +
+ ); +} +``` + +## 📚 API Reference + +### `useFs` Hook + The hook provides several key features: 1. **File System Access**: Prompts users to select a directory and maintains access to it. @@ -164,7 +285,7 @@ The hook provides several key features: - Debounced updates - Efficient change detection -### Props +#### Props - `filters?: FilterFn[]` - Array of filter functions to exclude files/directories - `onFilesAdded?: (newFiles: Map, previousFiles: Map) => void` - Callback when files are added @@ -175,7 +296,7 @@ The hook provides several key features: - `debounceInterval?: number` - Debounce interval for updates (default: 50ms) - `fileCacheTtl?: number` - How long to cache file contents (default: 5000ms) -### Return Values +#### Return Values - `onDirectorySelection: () => Promise` - Function to open directory picker - `onClear: () => void` - Function to stop watching and clear state @@ -188,6 +309,269 @@ The hook provides several key features: - `stopPolling: () => void` - Function to manually stop polling for file changes - `isPolling: boolean` - Whether the hook is actively polling for changes +### `useOPFS` Hook + +The `useOPFS` hook provides a simple API for working with the Origin Private File System without user permission prompts. + +#### Props + +- `onError?: (error: Error) => void` - Optional callback for error handling + +#### Return Values + +- `isSupported: boolean` - Whether OPFS is supported in the current browser +- `readFile: (path: string) => Promise` - Read a file's content as text +- `writeFile: (path: string, content: string | ArrayBuffer | Blob) => Promise` - Write content to a file +- `deleteFile: (path: string) => Promise` - Delete a file +- `createDirectory: (path: string) => Promise` - Create a directory (creates parent directories as needed) +- `deleteDirectory: (path: string, recursive?: boolean) => Promise` - Delete a directory +- `listFiles: (path?: string) => Promise` - List files and directories in a path +- `exists: (path: string) => Promise` - Check if a file or directory exists +- `getFileInfo: (path: string) => Promise` - Get detailed information about a file or directory + +#### Types + +```typescript +interface OPFSFileInfo { + name: string; // File/directory name + path: string; // Full path + kind: "file" | "directory"; + size?: number; // File size (only for files) +} +``` + + +## 📚 Contributing + +### Development + +1. Navigate to the `docs` directory +2. Run `npm install` to install the dependencies +3. Run `npm dev` to start the development server +4. Navigate to `http://localhost:3000` to view the demo. +5. Modify the `Demo.tsx` file to make your changes. + +If you're making changes to the `use-fs` package, you can run `npm build` to build the package and then run `npm link use-fs` to link the package to the `docs` directory for local development and testing. + +### Example: Simple OPFS Note-Taking App + +Here's a complete example of using `useOPFS` to build a simple note-taking application: + +```tsx +import React, { useState, useEffect } from 'react'; +import { useOPFS } from 'use-fs'; + +function NotesApp() { + const [notes, setNotes] = useState([]); + const [currentNote, setCurrentNote] = useState(''); + const [selectedNote, setSelectedNote] = useState(null); + const [noteContent, setNoteContent] = useState(''); + + const { + isSupported, + readFile, + writeFile, + deleteFile, + listFiles, + createDirectory + } = useOPFS({ + onError: (error) => { + console.error('OPFS Error:', error); + alert(`Error: ${error.message}`); + } + }); + + // Load notes list on mount + useEffect(() => { + if (isSupported) { + loadNotes(); + } + }, [isSupported]); + + const loadNotes = async () => { + try { + // Ensure notes directory exists + await createDirectory('notes'); + + // List all notes + const files = await listFiles('notes'); + const noteFiles = files + .filter(file => file.kind === 'file' && file.name.endsWith('.txt')) + .map(file => file.name.replace('.txt', '')); + + setNotes(noteFiles); + } catch (error) { + console.error('Failed to load notes:', error); + } + }; + + const saveNote = async () => { + if (!currentNote.trim()) { + alert('Please enter a note name'); + return; + } + + try { + await writeFile(`notes/${currentNote}.txt`, noteContent); + await loadNotes(); // Refresh notes list + setCurrentNote(''); + setNoteContent(''); + alert('Note saved successfully!'); + } catch (error) { + console.error('Failed to save note:', error); + } + }; + + const loadNote = async (noteName: string) => { + try { + const content = await readFile(`notes/${noteName}.txt`); + setSelectedNote(noteName); + setNoteContent(content); + } catch (error) { + console.error('Failed to load note:', error); + } + }; + + const deleteNote = async (noteName: string) => { + if (!confirm(`Delete note "${noteName}"?`)) return; + + try { + await deleteFile(`notes/${noteName}.txt`); + await loadNotes(); // Refresh notes list + if (selectedNote === noteName) { + setSelectedNote(null); + setNoteContent(''); + } + alert('Note deleted successfully!'); + } catch (error) { + console.error('Failed to delete note:', error); + } + }; + + if (!isSupported) { + return ( +
+

Notes App

+

Sorry, your browser doesn't support OPFS (Origin Private File System).

+

Please use a modern browser like Chrome, Firefox, or Safari.

+
+ ); + } + + return ( +
+

📝 OPFS Notes App

+ + {/* Create new note */} +
+

Create New Note

+ setCurrentNote(e.target.value)} + style={{ marginRight: '10px', padding: '5px' }} + /> +

+