mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 11:00:47 +02:00
File handling fixes
This commit is contained in:
@@ -19,15 +19,17 @@ import { useTranslation } from 'react-i18next';
|
||||
interface FilePickerModalProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
sharedFiles: any[];
|
||||
onSelectFiles: (selectedFiles: any[]) => void;
|
||||
storedFiles: any[]; // Files from storage (FileWithUrl format)
|
||||
onSelectFiles: (selectedFiles: File[]) => void;
|
||||
allowMultiple?: boolean;
|
||||
}
|
||||
|
||||
const FilePickerModal = ({
|
||||
opened,
|
||||
onClose,
|
||||
sharedFiles,
|
||||
storedFiles,
|
||||
onSelectFiles,
|
||||
allowMultiple = true,
|
||||
}: FilePickerModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedFileIds, setSelectedFileIds] = useState<string[]>([]);
|
||||
@@ -40,15 +42,22 @@ const FilePickerModal = ({
|
||||
}, [opened]);
|
||||
|
||||
const toggleFileSelection = (fileId: string) => {
|
||||
setSelectedFileIds(prev =>
|
||||
prev.includes(fileId)
|
||||
? prev.filter(id => id !== fileId)
|
||||
: [...prev, fileId]
|
||||
);
|
||||
setSelectedFileIds(prev => {
|
||||
if (allowMultiple) {
|
||||
return prev.includes(fileId)
|
||||
? prev.filter(id => id !== fileId)
|
||||
: [...prev, fileId];
|
||||
} else {
|
||||
// Single selection mode
|
||||
return prev.includes(fileId) ? [] : [fileId];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const selectAll = () => {
|
||||
setSelectedFileIds(sharedFiles.map(f => f.id || f.name));
|
||||
if (allowMultiple) {
|
||||
setSelectedFileIds(storedFiles.map(f => f.id || f.name));
|
||||
}
|
||||
};
|
||||
|
||||
const selectNone = () => {
|
||||
@@ -56,56 +65,54 @@ const FilePickerModal = ({
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const selectedFiles = sharedFiles.filter(f =>
|
||||
const selectedFiles = storedFiles.filter(f =>
|
||||
selectedFileIds.includes(f.id || f.name)
|
||||
);
|
||||
|
||||
// Convert FileWithUrl objects to proper File objects if needed
|
||||
// Convert stored files to File objects
|
||||
const convertedFiles = await Promise.all(
|
||||
selectedFiles.map(async (fileItem) => {
|
||||
console.log('Converting file item:', fileItem);
|
||||
|
||||
// If it's already a File object, return as is
|
||||
if (fileItem instanceof File) {
|
||||
console.log('File is already a File object');
|
||||
return fileItem;
|
||||
}
|
||||
|
||||
// If it has a file property, use that
|
||||
if (fileItem.file && fileItem.file instanceof File) {
|
||||
console.log('Using .file property');
|
||||
return fileItem.file;
|
||||
}
|
||||
|
||||
// If it's a FileWithUrl from storage, reconstruct the File
|
||||
if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') {
|
||||
try {
|
||||
console.log('Reconstructing file from storage:', fileItem.name, fileItem);
|
||||
try {
|
||||
// If it's already a File object, return as is
|
||||
if (fileItem instanceof File) {
|
||||
return fileItem;
|
||||
}
|
||||
|
||||
// If it has a file property, use that
|
||||
if (fileItem.file && fileItem.file instanceof File) {
|
||||
return fileItem.file;
|
||||
}
|
||||
|
||||
// If it's from IndexedDB storage, reconstruct the File
|
||||
if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') {
|
||||
const arrayBuffer = await fileItem.arrayBuffer();
|
||||
console.log('Got arrayBuffer:', arrayBuffer);
|
||||
|
||||
const blob = new Blob([arrayBuffer], { type: fileItem.type || 'application/pdf' });
|
||||
console.log('Created blob:', blob);
|
||||
|
||||
const reconstructedFile = new File([blob], fileItem.name, {
|
||||
return new File([blob], fileItem.name, {
|
||||
type: fileItem.type || 'application/pdf',
|
||||
lastModified: fileItem.lastModified || Date.now()
|
||||
});
|
||||
console.log('Reconstructed file:', reconstructedFile, 'instanceof File:', reconstructedFile instanceof File);
|
||||
return reconstructedFile;
|
||||
} catch (error) {
|
||||
console.error('Error reconstructing file:', error, fileItem);
|
||||
return null;
|
||||
}
|
||||
|
||||
// If it has data property, reconstruct the File
|
||||
if (fileItem.data) {
|
||||
const blob = new Blob([fileItem.data], { type: fileItem.type || 'application/pdf' });
|
||||
return new File([blob], fileItem.name, {
|
||||
type: fileItem.type || 'application/pdf',
|
||||
lastModified: fileItem.lastModified || Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
console.warn('Could not convert file item:', fileItem);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error converting file:', error, fileItem);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('No valid conversion method found for:', fileItem);
|
||||
return null; // Don't return invalid objects
|
||||
})
|
||||
);
|
||||
|
||||
// Filter out any null values from failed conversions
|
||||
const validFiles = convertedFiles.filter(f => f !== null);
|
||||
// Filter out any null values and return valid Files
|
||||
const validFiles = convertedFiles.filter((f): f is File => f !== null);
|
||||
|
||||
onSelectFiles(validFiles);
|
||||
onClose();
|
||||
@@ -128,7 +135,7 @@ const FilePickerModal = ({
|
||||
scrollAreaComponent={ScrollArea.Autosize}
|
||||
>
|
||||
<Stack gap="md">
|
||||
{sharedFiles.length === 0 ? (
|
||||
{storedFiles.length === 0 ? (
|
||||
<Text c="dimmed" ta="center" py="xl">
|
||||
{t("fileUpload.noFilesInStorage", "No files available in storage. Upload some files first.")}
|
||||
</Text>
|
||||
@@ -137,22 +144,27 @@ const FilePickerModal = ({
|
||||
{/* Selection controls */}
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">
|
||||
{sharedFiles.length} {t("fileUpload.filesAvailable", "files available")}
|
||||
{storedFiles.length} {t("fileUpload.filesAvailable", "files available")}
|
||||
{allowMultiple && selectedFileIds.length > 0 && (
|
||||
<> • {selectedFileIds.length} selected</>
|
||||
)}
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
<Button size="xs" variant="light" onClick={selectAll}>
|
||||
{t("pageEdit.selectAll", "Select All")}
|
||||
</Button>
|
||||
<Button size="xs" variant="light" onClick={selectNone}>
|
||||
{t("pageEdit.deselectAll", "Select None")}
|
||||
</Button>
|
||||
</Group>
|
||||
{allowMultiple && (
|
||||
<Group gap="xs">
|
||||
<Button size="xs" variant="light" onClick={selectAll}>
|
||||
{t("pageEdit.selectAll", "Select All")}
|
||||
</Button>
|
||||
<Button size="xs" variant="light" onClick={selectNone}>
|
||||
{t("pageEdit.deselectAll", "Select None")}
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{/* File grid */}
|
||||
<ScrollArea.Autosize mah={400}>
|
||||
<SimpleGrid cols={2} spacing="md">
|
||||
{sharedFiles.map((file) => {
|
||||
{storedFiles.map((file) => {
|
||||
const fileId = file.id || file.name;
|
||||
const isSelected = selectedFileIds.includes(fileId);
|
||||
|
||||
@@ -174,11 +186,21 @@ const FilePickerModal = ({
|
||||
onClick={() => toggleFileSelection(fileId)}
|
||||
>
|
||||
<Group gap="sm" align="flex-start">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={() => toggleFileSelection(fileId)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{allowMultiple ? (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={() => toggleFileSelection(fileId)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="radio"
|
||||
checked={isSelected}
|
||||
onChange={() => toggleFileSelection(fileId)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{ margin: '4px' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Thumbnail */}
|
||||
<Box
|
||||
|
||||
@@ -143,8 +143,9 @@ const FileUploadSelector = ({
|
||||
<FilePickerModal
|
||||
opened={showFilePickerModal}
|
||||
onClose={() => setShowFilePickerModal(false)}
|
||||
sharedFiles={sharedFiles}
|
||||
storedFiles={sharedFiles}
|
||||
onSelectFiles={handleStorageSelection}
|
||||
allowMultiple={allowMultiple}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user