2025-08-21 17:30:26 +01:00
|
|
|
/**
|
|
|
|
* File selectors - Pure functions for accessing file state
|
|
|
|
*/
|
|
|
|
|
2025-08-28 10:56:07 +01:00
|
|
|
import { FileId } from '../../types/file';
|
|
|
|
import {
|
|
|
|
FileRecord,
|
2025-08-21 17:30:26 +01:00
|
|
|
FileContextState,
|
2025-08-28 10:56:07 +01:00
|
|
|
FileContextSelectors
|
2025-08-21 17:30:26 +01:00
|
|
|
} from '../../types/fileContext';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create stable selectors using stateRef and filesRef
|
|
|
|
*/
|
|
|
|
export function createFileSelectors(
|
|
|
|
stateRef: React.MutableRefObject<FileContextState>,
|
|
|
|
filesRef: React.MutableRefObject<Map<FileId, File>>
|
|
|
|
): FileContextSelectors {
|
|
|
|
return {
|
|
|
|
getFile: (id: FileId) => filesRef.current.get(id),
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
getFiles: (ids?: FileId[]) => {
|
|
|
|
const currentIds = ids || stateRef.current.files.ids;
|
|
|
|
return currentIds.map(id => filesRef.current.get(id)).filter(Boolean) as File[];
|
|
|
|
},
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
getFileRecord: (id: FileId) => stateRef.current.files.byId[id],
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
getFileRecords: (ids?: FileId[]) => {
|
|
|
|
const currentIds = ids || stateRef.current.files.ids;
|
|
|
|
return currentIds.map(id => stateRef.current.files.byId[id]).filter(Boolean);
|
|
|
|
},
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
getAllFileIds: () => stateRef.current.files.ids,
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
getSelectedFiles: () => {
|
|
|
|
return stateRef.current.ui.selectedFileIds
|
|
|
|
.map(id => filesRef.current.get(id))
|
|
|
|
.filter(Boolean) as File[];
|
|
|
|
},
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
getSelectedFileRecords: () => {
|
|
|
|
return stateRef.current.ui.selectedFileIds
|
|
|
|
.map(id => stateRef.current.files.byId[id])
|
|
|
|
.filter(Boolean);
|
|
|
|
},
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
// Pinned files selectors
|
|
|
|
getPinnedFileIds: () => {
|
|
|
|
return Array.from(stateRef.current.pinnedFiles);
|
|
|
|
},
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
getPinnedFiles: () => {
|
|
|
|
return Array.from(stateRef.current.pinnedFiles)
|
|
|
|
.map(id => filesRef.current.get(id))
|
|
|
|
.filter(Boolean) as File[];
|
|
|
|
},
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
getPinnedFileRecords: () => {
|
|
|
|
return Array.from(stateRef.current.pinnedFiles)
|
|
|
|
.map(id => stateRef.current.files.byId[id])
|
|
|
|
.filter(Boolean);
|
|
|
|
},
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
isFilePinned: (file: File) => {
|
|
|
|
// Find FileId by matching File object properties
|
2025-08-28 10:56:07 +01:00
|
|
|
const fileId = (Object.keys(stateRef.current.files.byId) as FileId[]).find(id => {
|
2025-08-21 17:30:26 +01:00
|
|
|
const storedFile = filesRef.current.get(id);
|
2025-08-28 10:56:07 +01:00
|
|
|
return storedFile &&
|
|
|
|
storedFile.name === file.name &&
|
|
|
|
storedFile.size === file.size &&
|
2025-08-21 17:30:26 +01:00
|
|
|
storedFile.lastModified === file.lastModified;
|
|
|
|
});
|
|
|
|
return fileId ? stateRef.current.pinnedFiles.has(fileId) : false;
|
|
|
|
},
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
// Stable signature for effects - prevents unnecessary re-renders
|
|
|
|
getFilesSignature: () => {
|
|
|
|
return stateRef.current.files.ids
|
|
|
|
.map(id => {
|
|
|
|
const record = stateRef.current.files.byId[id];
|
|
|
|
return record ? `${id}:${record.size}:${record.lastModified}` : '';
|
|
|
|
})
|
|
|
|
.join('|');
|
|
|
|
},
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper for building quickKey sets for deduplication
|
|
|
|
*/
|
|
|
|
export function buildQuickKeySet(fileRecords: Record<FileId, FileRecord>): Set<string> {
|
|
|
|
const quickKeys = new Set<string>();
|
|
|
|
Object.values(fileRecords).forEach(record => {
|
|
|
|
if (record.quickKey) {
|
|
|
|
quickKeys.add(record.quickKey);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return quickKeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper for building quickKey sets from IndexedDB metadata
|
|
|
|
*/
|
|
|
|
export function buildQuickKeySetFromMetadata(metadata: Array<{ name: string; size: number; lastModified: number }>): Set<string> {
|
|
|
|
const quickKeys = new Set<string>();
|
|
|
|
metadata.forEach(meta => {
|
|
|
|
// Format: name|size|lastModified (same as createQuickKey)
|
|
|
|
const quickKey = `${meta.name}|${meta.size}|${meta.lastModified}`;
|
|
|
|
quickKeys.add(quickKey);
|
|
|
|
});
|
|
|
|
return quickKeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get primary file (first in list) - commonly used pattern
|
|
|
|
*/
|
|
|
|
export function getPrimaryFile(
|
|
|
|
stateRef: React.MutableRefObject<FileContextState>,
|
|
|
|
filesRef: React.MutableRefObject<Map<FileId, File>>
|
|
|
|
): { file?: File; record?: FileRecord } {
|
|
|
|
const primaryFileId = stateRef.current.files.ids[0];
|
|
|
|
if (!primaryFileId) return {};
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
return {
|
|
|
|
file: filesRef.current.get(primaryFileId),
|
|
|
|
record: stateRef.current.files.byId[primaryFileId]
|
|
|
|
};
|
2025-08-28 10:56:07 +01:00
|
|
|
}
|