ConnorYoh 7e3321ee16
Feature/v2/filemanager (#4121)
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>
2025-08-08 15:15:09 +01:00

83 lines
2.8 KiB
TypeScript

import React from 'react';
import { Stack, Box } from '@mantine/core';
import FileSourceButtons from './FileSourceButtons';
import FileDetails from './FileDetails';
import SearchInput from './SearchInput';
import FileListArea from './FileListArea';
import HiddenFileInput from './HiddenFileInput';
import { useFileManagerContext } from '../../contexts/FileManagerContext';
const MobileLayout: React.FC = () => {
const {
activeSource,
selectedFiles,
modalHeight,
} = useFileManagerContext();
// Calculate the height more accurately based on actual content
const calculateFileListHeight = () => {
// Base modal height minus padding and gaps
const baseHeight = `calc(${modalHeight} - 2rem)`; // Account for Stack padding
// Estimate heights of fixed components
const fileSourceHeight = '3rem'; // FileSourceButtons height
const fileDetailsHeight = selectedFiles.length > 0 ? '10rem' : '8rem'; // FileDetails compact height
const searchHeight = activeSource === 'recent' ? '3rem' : '0rem'; // SearchInput height
const gapHeight = activeSource === 'recent' ? '3rem' : '2rem'; // Stack gaps
return `calc(${baseHeight} - ${fileSourceHeight} - ${fileDetailsHeight} - ${searchHeight} - ${gapHeight})`;
};
return (
<Box h="100%" p="sm" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
{/* Section 1: File Sources - Fixed at top */}
<Box style={{ flexShrink: 0 }}>
<FileSourceButtons horizontal={true} />
</Box>
<Box style={{ flexShrink: 0 }}>
<FileDetails compact={true} />
</Box>
{/* Section 3 & 4: Search Bar + File List - Unified background extending to modal edge */}
<Box style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
backgroundColor: 'var(--bg-file-list)',
borderRadius: '0.5rem',
border: '1px solid var(--mantine-color-gray-2)',
overflow: 'hidden',
minHeight: 0
}}>
{activeSource === 'recent' && (
<Box style={{
flexShrink: 0,
borderBottom: '1px solid var(--mantine-color-gray-2)'
}}>
<SearchInput />
</Box>
)}
<Box style={{ flex: 1, minHeight: 0 }}>
<FileListArea
scrollAreaHeight={calculateFileListHeight()}
scrollAreaStyle={{
height: calculateFileListHeight(),
maxHeight: '60vh',
minHeight: '9.375rem',
backgroundColor: 'transparent',
border: 'none',
borderRadius: 0
}}
/>
</Box>
</Box>
{/* Hidden file input for local file selection */}
<HiddenFileInput />
</Box>
);
};
export default MobileLayout;