Reece Browne 949ffa01ad
Feature/v2/file handling improvements (#4222)
# Description of Changes

A new universal file context rather than the splintered ones for the
main views, tools and manager we had before (manager still has its own
but its better integreated with the core context)
File context has been split it into a handful of different files
managing various file related issues separately to reduce the monolith -
FileReducer.ts - State management
  fileActions.ts - File operations
  fileSelectors.ts - Data access patterns
  lifecycle.ts - Resource cleanup and memory management
  fileHooks.ts - React hooks interface
  contexts.ts - Context providers
Improved thumbnail generation
Improved indexxedb handling
Stopped handling files as blobs were not necessary to improve
performance
A new library handling drag and drop
https://github.com/atlassian/pragmatic-drag-and-drop (Out of scope yes
but I broke the old one with the new filecontext and it needed doing so
it was a might as well)
A new library handling virtualisation on page editor
@tanstack/react-virtual, as above.
Quickly ripped out the last remnants of the old URL params stuff and
replaced with the beginnings of what will later become the new URL
navigation system (for now it just restores the tool name in url
behavior)
Fixed selected file not regestered when opening a tool
Fixed png thumbnails
Closes #(issue_number)

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: Reece Browne <you@example.com>
2025-08-21 17:30:26 +01:00

194 lines
5.6 KiB
TypeScript

/**
* Performant file hooks - Clean API using FileContext
*/
import { useContext, useMemo } from 'react';
import {
FileStateContext,
FileActionsContext,
FileContextStateValue,
FileContextActionsValue
} from './contexts';
import { FileId, FileRecord } from '../../types/fileContext';
/**
* Hook for accessing file state (will re-render on any state change)
* Use individual selector hooks below for better performance
*/
export function useFileState(): FileContextStateValue {
const context = useContext(FileStateContext);
if (!context) {
throw new Error('useFileState must be used within a FileContextProvider');
}
return context;
}
/**
* Hook for accessing file actions (stable - won't cause re-renders)
*/
export function useFileActions(): FileContextActionsValue {
const context = useContext(FileActionsContext);
if (!context) {
throw new Error('useFileActions must be used within a FileContextProvider');
}
return context;
}
/**
* Hook for current/primary file (first in list)
*/
export function useCurrentFile(): { file?: File; record?: FileRecord } {
const { state, selectors } = useFileState();
const primaryFileId = state.files.ids[0];
return useMemo(() => ({
file: primaryFileId ? selectors.getFile(primaryFileId) : undefined,
record: primaryFileId ? selectors.getFileRecord(primaryFileId) : undefined
}), [primaryFileId, selectors]);
}
/**
* Hook for file selection state and actions
*/
export function useFileSelection() {
const { state, selectors } = useFileState();
const { actions } = useFileActions();
// Memoize selected files to avoid recreating arrays
const selectedFiles = useMemo(() => {
return selectors.getSelectedFiles();
}, [state.ui.selectedFileIds, selectors]);
return useMemo(() => ({
selectedFiles,
selectedFileIds: state.ui.selectedFileIds,
selectedPageNumbers: state.ui.selectedPageNumbers,
setSelectedFiles: actions.setSelectedFiles,
setSelectedPages: actions.setSelectedPages,
clearSelections: actions.clearSelections
}), [
selectedFiles,
state.ui.selectedFileIds,
state.ui.selectedPageNumbers,
actions.setSelectedFiles,
actions.setSelectedPages,
actions.clearSelections
]);
}
/**
* Hook for file management operations
*/
export function useFileManagement() {
const { actions } = useFileActions();
return useMemo(() => ({
addFiles: actions.addFiles,
removeFiles: actions.removeFiles,
clearAllFiles: actions.clearAllFiles,
updateFileRecord: actions.updateFileRecord,
reorderFiles: actions.reorderFiles
}), [actions]);
}
/**
* Hook for UI state
*/
export function useFileUI() {
const { state } = useFileState();
const { actions } = useFileActions();
return useMemo(() => ({
isProcessing: state.ui.isProcessing,
processingProgress: state.ui.processingProgress,
hasUnsavedChanges: state.ui.hasUnsavedChanges,
setProcessing: actions.setProcessing,
setUnsavedChanges: actions.setHasUnsavedChanges
}), [state.ui, actions]);
}
/**
* Hook for specific file by ID (optimized for individual file access)
*/
export function useFileRecord(fileId: FileId): { file?: File; record?: FileRecord } {
const { selectors } = useFileState();
return useMemo(() => ({
file: selectors.getFile(fileId),
record: selectors.getFileRecord(fileId)
}), [fileId, selectors]);
}
/**
* Hook for all files (use sparingly - causes re-renders on file list changes)
*/
export function useAllFiles(): { files: File[]; records: FileRecord[]; fileIds: FileId[] } {
const { state, selectors } = useFileState();
return useMemo(() => ({
files: selectors.getFiles(),
records: selectors.getFileRecords(),
fileIds: state.files.ids
}), [state.files.ids, selectors]);
}
/**
* Hook for selected files (optimized for selection-based UI)
*/
export function useSelectedFiles(): { files: File[]; records: FileRecord[]; fileIds: FileId[] } {
const { state, selectors } = useFileState();
return useMemo(() => ({
files: selectors.getSelectedFiles(),
records: selectors.getSelectedFileRecords(),
fileIds: state.ui.selectedFileIds
}), [state.ui.selectedFileIds, selectors]);
}
// Navigation management removed - moved to NavigationContext
/**
* Primary API hook for file context operations
* Used by tools for core file context functionality
*/
export function useFileContext() {
const { state, selectors } = useFileState();
const { actions } = useFileActions();
return useMemo(() => ({
// Lifecycle management
trackBlobUrl: actions.trackBlobUrl,
scheduleCleanup: actions.scheduleCleanup,
setUnsavedChanges: actions.setHasUnsavedChanges,
// File management
addFiles: actions.addFiles,
consumeFiles: actions.consumeFiles,
recordOperation: (fileId: string, operation: any) => {}, // Operation tracking not implemented
markOperationApplied: (fileId: string, operationId: string) => {}, // Operation tracking not implemented
markOperationFailed: (fileId: string, operationId: string, error: string) => {}, // Operation tracking not implemented
// File ID lookup
findFileId: (file: File) => {
return state.files.ids.find(id => {
const record = state.files.byId[id];
return record &&
record.name === file.name &&
record.size === file.size &&
record.lastModified === file.lastModified;
});
},
// Pinned files
pinnedFiles: state.pinnedFiles,
pinFile: actions.pinFile,
unpinFile: actions.unpinFile,
isFilePinned: selectors.isFilePinned,
// Active files
activeFiles: selectors.getFiles()
}), [state, selectors, actions]);
}