mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
FileManager Component Overview
Purpose: Modal component for selecting and managing PDF files with
preview capabilities
Architecture:
- Responsive Layouts: MobileLayout.tsx (stacked) vs DesktopLayout.tsx
(3-column)
- Central State: FileManagerContext handles file operations, selection,
and modal state
- File Storage: IndexedDB persistence with thumbnail caching
Key Components:
- FileSourceButtons: Switch between Recent/Local/Drive sources
- FileListArea: Scrollable file grid with search functionality
- FilePreview: PDF thumbnails with dynamic shadow stacking (1-2 shadow
pages based on file count)
- FileDetails: File info card with metadata
- CompactFileDetails: Mobile-optimized file info layout
File Flow:
1. Users select source → browse/search files → select multiple files →
preview with navigation → open in
tools
2. Files persist across tool switches via FileContext integration
3. Memory management handles large PDFs (up to 100GB+)
```mermaid
graph TD
FM[FileManager] --> ML[MobileLayout]
FM --> DL[DesktopLayout]
ML --> FSB[FileSourceButtons<br/>Recent/Local/Drive]
ML --> FLA[FileListArea]
ML --> FD[FileDetails]
DL --> FSB
DL --> FLA
DL --> FD
FLA --> FLI[FileListItem]
FD --> FP[FilePreview]
FD --> CFD[CompactFileDetails]
```
---------
Co-authored-by: Connor Yoh <[email protected]>
126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
import React from 'react';
|
|
import { Stack, Box, Text, Button, ActionIcon, Center } from '@mantine/core';
|
|
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
|
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { getFileSize } from '../../utils/fileUtils';
|
|
import { FileWithUrl } from '../../types/file';
|
|
|
|
interface CompactFileDetailsProps {
|
|
currentFile: FileWithUrl | null;
|
|
thumbnail: string | null;
|
|
selectedFiles: FileWithUrl[];
|
|
currentFileIndex: number;
|
|
numberOfFiles: number;
|
|
isAnimating: boolean;
|
|
onPrevious: () => void;
|
|
onNext: () => void;
|
|
onOpenFiles: () => void;
|
|
}
|
|
|
|
const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
|
currentFile,
|
|
thumbnail,
|
|
selectedFiles,
|
|
currentFileIndex,
|
|
numberOfFiles,
|
|
isAnimating,
|
|
onPrevious,
|
|
onNext,
|
|
onOpenFiles
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const hasSelection = selectedFiles.length > 0;
|
|
const hasMultipleFiles = numberOfFiles > 1;
|
|
|
|
return (
|
|
<Stack gap="xs" style={{ height: '100%' }}>
|
|
{/* Compact mobile layout */}
|
|
<Box style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
|
|
{/* Small preview */}
|
|
<Box style={{ width: '7.5rem', height: '9.375rem', flexShrink: 0, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
{currentFile && thumbnail ? (
|
|
<img
|
|
src={thumbnail}
|
|
alt={currentFile.name}
|
|
style={{
|
|
maxWidth: '100%',
|
|
maxHeight: '100%',
|
|
objectFit: 'contain',
|
|
borderRadius: '0.25rem',
|
|
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)'
|
|
}}
|
|
/>
|
|
) : currentFile ? (
|
|
<Center style={{
|
|
width: '100%',
|
|
height: '100%',
|
|
backgroundColor: 'var(--mantine-color-gray-1)',
|
|
borderRadius: 4
|
|
}}>
|
|
<PictureAsPdfIcon style={{ fontSize: 20, color: 'var(--mantine-color-gray-6)' }} />
|
|
</Center>
|
|
) : null}
|
|
</Box>
|
|
|
|
{/* File info */}
|
|
<Box style={{ flex: 1, minWidth: 0 }}>
|
|
<Text size="sm" fw={500} truncate>
|
|
{currentFile ? currentFile.name : 'No file selected'}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
{currentFile ? getFileSize(currentFile) : ''}
|
|
{selectedFiles.length > 1 && ` • ${selectedFiles.length} files`}
|
|
</Text>
|
|
{hasMultipleFiles && (
|
|
<Text size="xs" c="blue">
|
|
{currentFileIndex + 1} of {selectedFiles.length}
|
|
</Text>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Navigation arrows for multiple files */}
|
|
{hasMultipleFiles && (
|
|
<Box style={{ display: 'flex', gap: '0.25rem' }}>
|
|
<ActionIcon
|
|
variant="subtle"
|
|
size="sm"
|
|
onClick={onPrevious}
|
|
disabled={isAnimating}
|
|
>
|
|
<ChevronLeftIcon style={{ fontSize: 16 }} />
|
|
</ActionIcon>
|
|
<ActionIcon
|
|
variant="subtle"
|
|
size="sm"
|
|
onClick={onNext}
|
|
disabled={isAnimating}
|
|
>
|
|
<ChevronRightIcon style={{ fontSize: 16 }} />
|
|
</ActionIcon>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Action Button */}
|
|
<Button
|
|
size="sm"
|
|
onClick={onOpenFiles}
|
|
disabled={!hasSelection}
|
|
fullWidth
|
|
style={{
|
|
backgroundColor: hasSelection ? 'var(--btn-open-file)' : 'var(--mantine-color-gray-4)',
|
|
color: 'white'
|
|
}}
|
|
>
|
|
{selectedFiles.length > 1
|
|
? t('fileManager.openFiles', `Open ${selectedFiles.length} Files`)
|
|
: t('fileManager.openFile', 'Open File')
|
|
}
|
|
</Button>
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
export default CompactFileDetails; |