Files
Stirling-PDF/frontend/src/components/shared/FileUploadSelector.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

172 lines
5.2 KiB
TypeScript
Raw Normal View History

import React, { useState, useCallback, useRef } from 'react';
2025-06-20 17:51:24 +01:00
import { Stack, Button, Text, Center } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import UploadFileIcon from '@mui/icons-material/UploadFile';
import { useTranslation } from 'react-i18next';
import FilePickerModal from './FilePickerModal';
interface FileUploadSelectorProps {
// Appearance
title?: string;
subtitle?: string;
showDropzone?: boolean;
2025-06-20 20:22:18 +01:00
2025-06-20 17:51:24 +01:00
// File handling
sharedFiles?: any[];
onFileSelect?: (file: File) => void;
onFilesSelect: (files: File[]) => void;
2025-06-20 17:51:24 +01:00
accept?: string[];
2025-06-20 20:22:18 +01:00
2025-06-20 17:51:24 +01:00
// Loading state
loading?: boolean;
disabled?: boolean;
}
const FileUploadSelector = ({
2025-06-20 21:46:37 +01:00
title,
subtitle,
2025-06-20 17:51:24 +01:00
showDropzone = true,
sharedFiles = [],
onFileSelect,
onFilesSelect,
accept = ["application/pdf"],
loading = false,
disabled = false,
}: FileUploadSelectorProps) => {
const { t } = useTranslation();
const [showFilePickerModal, setShowFilePickerModal] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
2025-06-20 17:51:24 +01:00
const handleFileUpload = useCallback((uploadedFiles: File[]) => {
if (uploadedFiles.length === 0) return;
2025-06-20 20:22:18 +01:00
if (onFilesSelect) {
2025-06-20 17:51:24 +01:00
onFilesSelect(uploadedFiles);
} else if (onFileSelect) {
2025-06-20 17:51:24 +01:00
onFileSelect(uploadedFiles[0]);
}
}, [onFileSelect, onFilesSelect]);
const handleFileInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files.length > 0) {
const fileArray = Array.from(files);
console.log('File input change:', fileArray.length, 'files');
handleFileUpload(fileArray);
}
// Reset input
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
}, [handleFileUpload]);
const openFileDialog = useCallback(() => {
fileInputRef.current?.click();
}, []);
2025-06-20 17:51:24 +01:00
const handleStorageSelection = useCallback((selectedFiles: File[]) => {
if (selectedFiles.length === 0) return;
2025-06-20 20:22:18 +01:00
if (onFilesSelect) {
2025-06-20 17:51:24 +01:00
onFilesSelect(selectedFiles);
} else if (onFileSelect) {
2025-06-20 17:51:24 +01:00
onFileSelect(selectedFiles[0]);
}
}, [onFileSelect, onFilesSelect]);
2025-06-20 17:51:24 +01:00
2025-06-20 21:46:37 +01:00
// Get default title and subtitle from translations if not provided
const displayTitle = title || t("fileUpload.selectFiles", "Select files");
const displaySubtitle = subtitle || t("fileUpload.chooseFromStorageMultiple", "Choose files from storage or upload new PDFs");
2025-06-20 21:46:37 +01:00
2025-06-20 17:51:24 +01:00
return (
<>
<Stack align="center" gap="xl">
{/* Title and description */}
<Stack align="center" gap="md">
<UploadFileIcon style={{ fontSize: 64 }} />
<Text size="xl" fw={500}>
2025-06-20 21:46:37 +01:00
{displayTitle}
2025-06-20 17:51:24 +01:00
</Text>
<Text size="md" c="dimmed">
2025-06-20 21:46:37 +01:00
{displaySubtitle}
2025-06-20 17:51:24 +01:00
</Text>
</Stack>
{/* Action buttons */}
<Stack align="center" gap="md" w="100%">
<Button
variant="filled"
size="lg"
onClick={() => setShowFilePickerModal(true)}
disabled={disabled || sharedFiles.length === 0}
loading={loading}
>
2025-06-20 23:00:26 +01:00
{loading ? "Loading..." : `Load from Storage (${sharedFiles.length} files available)`}
2025-06-20 17:51:24 +01:00
</Button>
2025-06-20 20:22:18 +01:00
2025-06-20 17:51:24 +01:00
<Text size="md" c="dimmed">
2025-06-20 21:46:37 +01:00
{t("fileUpload.or", "or")}
2025-06-20 17:51:24 +01:00
</Text>
2025-06-20 20:22:18 +01:00
2025-06-20 17:51:24 +01:00
{showDropzone ? (
<Dropzone
onDrop={handleFileUpload}
accept={accept}
multiple={true}
2025-06-20 17:51:24 +01:00
disabled={disabled || loading}
style={{ width: '100%', minHeight: 120 }}
activateOnClick={true}
2025-06-20 17:51:24 +01:00
>
<Center>
<Stack align="center" gap="sm">
<Text size="md" fw={500}>
{t("fileUpload.dropFilesHere", "Drop files here or click to upload")}
2025-06-20 17:51:24 +01:00
</Text>
<Text size="sm" c="dimmed">
2025-06-20 23:00:26 +01:00
{accept.includes('application/pdf')
2025-06-20 21:46:37 +01:00
? t("fileUpload.pdfFilesOnly", "PDF files only")
: t("fileUpload.supportedFileTypes", "Supported file types")
}
2025-06-20 17:51:24 +01:00
</Text>
</Stack>
</Center>
</Dropzone>
) : (
<Stack align="center" gap="sm">
2025-06-20 20:22:18 +01:00
<Button
variant="outline"
2025-06-20 17:51:24 +01:00
size="lg"
disabled={disabled}
loading={loading}
onClick={openFileDialog}
2025-06-20 17:51:24 +01:00
>
{t("fileUpload.uploadFiles", "Upload Files")}
2025-06-20 17:51:24 +01:00
</Button>
{/* Manual file input as backup */}
<input
ref={fileInputRef}
type="file"
multiple={true}
accept={accept.join(',')}
onChange={handleFileInputChange}
style={{ display: 'none' }}
/>
</Stack>
2025-06-20 17:51:24 +01:00
)}
</Stack>
</Stack>
{/* File Picker Modal */}
<FilePickerModal
opened={showFilePickerModal}
onClose={() => setShowFilePickerModal(false)}
2025-06-24 20:25:03 +01:00
storedFiles={sharedFiles}
2025-06-20 17:51:24 +01:00
onSelectFiles={handleStorageSelection}
/>
</>
);
};
2025-06-20 20:22:18 +01:00
export default FileUploadSelector;