mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00: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 <connor@stirlingpdf.com>
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Stack, Button } from '@mantine/core';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useIndexedDBThumbnail } from '../../hooks/useIndexedDBThumbnail';
|
|
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
|
import FilePreview from './FilePreview';
|
|
import FileInfoCard from './FileInfoCard';
|
|
import CompactFileDetails from './CompactFileDetails';
|
|
|
|
interface FileDetailsProps {
|
|
compact?: boolean;
|
|
}
|
|
|
|
const FileDetails: React.FC<FileDetailsProps> = ({
|
|
compact = false
|
|
}) => {
|
|
const { selectedFiles, onOpenFiles, modalHeight } = useFileManagerContext();
|
|
const { t } = useTranslation();
|
|
const [currentFileIndex, setCurrentFileIndex] = useState(0);
|
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
|
|
// Get the currently displayed file
|
|
const currentFile = selectedFiles.length > 0 ? selectedFiles[currentFileIndex] : null;
|
|
const hasSelection = selectedFiles.length > 0;
|
|
const hasMultipleFiles = selectedFiles.length > 1;
|
|
|
|
// Use IndexedDB hook for the current file
|
|
const { thumbnail: currentThumbnail } = useIndexedDBThumbnail(currentFile);
|
|
|
|
// Get thumbnail for current file
|
|
const getCurrentThumbnail = () => {
|
|
return currentThumbnail;
|
|
};
|
|
|
|
const handlePrevious = () => {
|
|
if (isAnimating) return;
|
|
setIsAnimating(true);
|
|
setTimeout(() => {
|
|
setCurrentFileIndex(prev => prev > 0 ? prev - 1 : selectedFiles.length - 1);
|
|
setIsAnimating(false);
|
|
}, 150);
|
|
};
|
|
|
|
const handleNext = () => {
|
|
if (isAnimating) return;
|
|
setIsAnimating(true);
|
|
setTimeout(() => {
|
|
setCurrentFileIndex(prev => prev < selectedFiles.length - 1 ? prev + 1 : 0);
|
|
setIsAnimating(false);
|
|
}, 150);
|
|
};
|
|
|
|
// Reset index when selection changes
|
|
React.useEffect(() => {
|
|
if (currentFileIndex >= selectedFiles.length) {
|
|
setCurrentFileIndex(0);
|
|
}
|
|
}, [selectedFiles.length, currentFileIndex]);
|
|
|
|
if (compact) {
|
|
return (
|
|
<CompactFileDetails
|
|
currentFile={currentFile}
|
|
thumbnail={getCurrentThumbnail()}
|
|
selectedFiles={selectedFiles}
|
|
currentFileIndex={currentFileIndex}
|
|
numberOfFiles={selectedFiles.length}
|
|
isAnimating={isAnimating}
|
|
onPrevious={handlePrevious}
|
|
onNext={handleNext}
|
|
onOpenFiles={onOpenFiles}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Stack gap="lg" h={`calc(${modalHeight} - 2rem)`}>
|
|
{/* Section 1: Thumbnail Preview */}
|
|
<FilePreview
|
|
currentFile={currentFile}
|
|
thumbnail={getCurrentThumbnail()}
|
|
numberOfFiles={selectedFiles.length}
|
|
isAnimating={isAnimating}
|
|
modalHeight={modalHeight}
|
|
onPrevious={handlePrevious}
|
|
onNext={handleNext}
|
|
/>
|
|
|
|
{/* Section 2: File Details */}
|
|
<FileInfoCard
|
|
currentFile={currentFile}
|
|
modalHeight={modalHeight}
|
|
/>
|
|
|
|
<Button
|
|
size="md"
|
|
mb="xl"
|
|
onClick={onOpenFiles}
|
|
disabled={!hasSelection}
|
|
fullWidth
|
|
style={{
|
|
flexShrink: 0,
|
|
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 FileDetails; |