Files
Stirling-PDF/frontend/src/desktop/hooks/useOpenedFile.ts
T
b8ce4e47c1 Preserve local paths for desktop saves (#5543)
# Summary

- Adds desktop file tracking: local paths are preserved and save buttons
now work as expcted (doing Save/Save As as appropriate)
- Adds logic to track whether files are 'dirty' (they've been modified
by some tool, and not saved to disk yet).
- Improves file state UX (dirty vs saved) and close warnings
- Web behaviour should be unaffected by these changes

## Indicators
Files now have indicators in desktop mode to tell you their state.

### File up-to-date with disk

<img width="318" height="393" alt="image"
src="https://github.com/user-attachments/assets/06325f9a-afd7-4c2f-8a5b-6d11e3093115"
/>

### File modified by a tool but not saved to disk yet

<img width="357" height="385" alt="image"
src="https://github.com/user-attachments/assets/1a7716d9-c6f7-4d13-be0d-c1de6493954b"
/>

### File not tracked on disk

<img width="312" height="379" alt="image"
src="https://github.com/user-attachments/assets/9cffe300-bd9a-4e19-97c7-9b98bebefacc"
/>

# Limitations
- It's a bit weird that we still have files stored in indexeddb in the
app, which are still loadable. We might want to change this behaviour in
the future
- Viewer's Save doesn't persist to disk. I've left that out here because
it'd need a lot of testing to make sure the logic's right with making
sure you can leave the Viewer with applying the changes to the PDF
_without_ saving to disk
- There's no current way to do Save As on a file that has already been
persisted to disk - it's only ever Save. Similarly, there's no way to
duplicate a file.

---------

Co-authored-by: James Brunton <[email protected]>
Co-authored-by: James Brunton <[email protected]>
2026-02-13 23:15:28 +00:00

62 lines
2.0 KiB
TypeScript

import { useState, useEffect, useRef, useCallback } from 'react';
import { fileOpenService } from '@app/services/fileOpenService';
import { listen } from '@tauri-apps/api/event';
export function useOpenedFile() {
const [openedFilePaths, setOpenedFilePaths] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const openedFilePathsRef = useRef<string[]>([]);
const clearOpenedFilePaths = useCallback(() => {
openedFilePathsRef.current = [];
setOpenedFilePaths([]);
}, []);
const consumeOpenedFilePaths = useCallback(() => {
const current = openedFilePathsRef.current;
openedFilePathsRef.current = [];
setOpenedFilePaths([]);
return current;
}, []);
useEffect(() => {
// Function to read and process files from storage
const readFilesFromStorage = async () => {
console.log('🔍 Reading files from storage...');
try {
const filePaths = await fileOpenService.getOpenedFiles();
console.log('🔍 fileOpenService.getOpenedFiles() returned:', filePaths);
if (filePaths.length > 0) {
console.log(`✅ Found ${filePaths.length} file(s) in storage:`, filePaths);
openedFilePathsRef.current = filePaths;
setOpenedFilePaths(filePaths);
}
} catch (error) {
console.error('❌ Failed to read files from storage:', error);
} finally {
setLoading(false);
}
};
// Read files on mount
readFilesFromStorage();
// Listen for files-changed events (when new files are added to storage)
let unlisten: (() => void) | undefined;
listen('files-changed', async () => {
console.log('📂 files-changed event received, re-reading storage...');
await readFilesFromStorage();
}).then(unlistenFn => {
unlisten = unlistenFn;
});
// Cleanup function
return () => {
if (unlisten) unlisten();
};
}, []);
return { openedFilePaths, loading, clearOpenedFilePaths, consumeOpenedFilePaths };
}