mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-23 12:49:24 +00:00

# Description of Changes <!-- Fixes the issues with I18 that were causing lots of console errors. Fixes mime type error Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com> Co-authored-by: Ludy <Ludy87@users.noreply.github.com> Co-authored-by: Dario Ghunney Ware <dariogware@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Co-authored-by: Ethan <ethan@MacBook-Pro.local> Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com> Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
169 lines
5.4 KiB
TypeScript
169 lines
5.4 KiB
TypeScript
import React, { useState, useCallback, useEffect } from 'react';
|
|
import { Modal } from '@mantine/core';
|
|
import { Dropzone } from '@mantine/dropzone';
|
|
import { FileWithUrl } from '../types/file';
|
|
import { useFileManager } from '../hooks/useFileManager';
|
|
import { useFilesModalContext } from '../contexts/FilesModalContext';
|
|
import { Tool } from '../types/tool';
|
|
import MobileLayout from './fileManager/MobileLayout';
|
|
import DesktopLayout from './fileManager/DesktopLayout';
|
|
import DragOverlay from './fileManager/DragOverlay';
|
|
import { FileManagerProvider } from '../contexts/FileManagerContext';
|
|
|
|
interface FileManagerProps {
|
|
selectedTool?: Tool | null;
|
|
}
|
|
|
|
const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
|
const { isFilesModalOpen, closeFilesModal, onFilesSelect } = useFilesModalContext();
|
|
const [recentFiles, setRecentFiles] = useState<FileWithUrl[]>([]);
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
const [isMobile, setIsMobile] = useState(false);
|
|
|
|
const { loadRecentFiles, handleRemoveFile, storeFile, convertToFile } = useFileManager();
|
|
|
|
// File management handlers
|
|
const isFileSupported = useCallback((fileName: string) => {
|
|
if (!selectedTool?.supportedFormats) return true;
|
|
const extension = fileName.split('.').pop()?.toLowerCase();
|
|
return selectedTool.supportedFormats.includes(extension || '');
|
|
}, [selectedTool?.supportedFormats]);
|
|
|
|
const refreshRecentFiles = useCallback(async () => {
|
|
const files = await loadRecentFiles();
|
|
setRecentFiles(files);
|
|
}, [loadRecentFiles]);
|
|
|
|
const handleFilesSelected = useCallback(async (files: FileWithUrl[]) => {
|
|
try {
|
|
const fileObjects = await Promise.all(
|
|
files.map(async (fileWithUrl) => {
|
|
return await convertToFile(fileWithUrl);
|
|
})
|
|
);
|
|
onFilesSelect(fileObjects);
|
|
} catch (error) {
|
|
console.error('Failed to process selected files:', error);
|
|
}
|
|
}, [convertToFile, onFilesSelect]);
|
|
|
|
const handleNewFileUpload = useCallback(async (files: File[]) => {
|
|
if (files.length > 0) {
|
|
try {
|
|
// Files will get IDs assigned through onFilesSelect -> FileContext addFiles
|
|
onFilesSelect(files);
|
|
await refreshRecentFiles();
|
|
} catch (error) {
|
|
console.error('Failed to process dropped files:', error);
|
|
}
|
|
}
|
|
}, [onFilesSelect, refreshRecentFiles]);
|
|
|
|
const handleRemoveFileByIndex = useCallback(async (index: number) => {
|
|
await handleRemoveFile(index, recentFiles, setRecentFiles);
|
|
}, [handleRemoveFile, recentFiles]);
|
|
|
|
useEffect(() => {
|
|
const checkMobile = () => setIsMobile(window.innerWidth < 1030);
|
|
checkMobile();
|
|
window.addEventListener('resize', checkMobile);
|
|
return () => window.removeEventListener('resize', checkMobile);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (isFilesModalOpen) {
|
|
refreshRecentFiles();
|
|
} else {
|
|
// Reset state when modal is closed
|
|
setIsDragging(false);
|
|
}
|
|
}, [isFilesModalOpen, refreshRecentFiles]);
|
|
|
|
// Cleanup any blob URLs when component unmounts
|
|
useEffect(() => {
|
|
return () => {
|
|
// Clean up blob URLs from recent files
|
|
recentFiles.forEach(file => {
|
|
if (file.url && file.url.startsWith('blob:')) {
|
|
URL.revokeObjectURL(file.url);
|
|
}
|
|
});
|
|
};
|
|
}, [recentFiles]);
|
|
|
|
// Modal size constants for consistent scaling
|
|
const modalHeight = '80vh';
|
|
const modalWidth = isMobile ? '100%' : '80vw';
|
|
const modalMaxWidth = isMobile ? '100%' : '1200px';
|
|
const modalMaxHeight = '1200px';
|
|
const modalMinWidth = isMobile ? '320px' : '800px';
|
|
|
|
return (
|
|
<Modal
|
|
opened={isFilesModalOpen}
|
|
onClose={closeFilesModal}
|
|
size={isMobile ? "100%" : "auto"}
|
|
centered
|
|
radius={30}
|
|
className="overflow-hidden p-0"
|
|
withCloseButton={false}
|
|
styles={{
|
|
content: {
|
|
position: 'relative',
|
|
margin: isMobile ? '1rem' : '2rem'
|
|
},
|
|
body: { padding: 0 },
|
|
header: { display: 'none' }
|
|
}}
|
|
>
|
|
<div style={{
|
|
position: 'relative',
|
|
height: modalHeight,
|
|
width: modalWidth,
|
|
maxWidth: modalMaxWidth,
|
|
maxHeight: modalMaxHeight,
|
|
minWidth: modalMinWidth,
|
|
margin: '0 auto',
|
|
overflow: 'hidden'
|
|
}}>
|
|
<Dropzone
|
|
onDrop={handleNewFileUpload}
|
|
onDragEnter={() => setIsDragging(true)}
|
|
onDragLeave={() => setIsDragging(false)}
|
|
accept={["*/*"] as any}
|
|
multiple={true}
|
|
activateOnClick={false}
|
|
style={{
|
|
height: '100%',
|
|
width: '100%',
|
|
border: 'none',
|
|
borderRadius: '30px',
|
|
backgroundColor: 'var(--bg-file-manager)'
|
|
}}
|
|
styles={{
|
|
inner: { pointerEvents: 'all' }
|
|
}}
|
|
>
|
|
<FileManagerProvider
|
|
recentFiles={recentFiles}
|
|
onFilesSelected={handleFilesSelected}
|
|
onClose={closeFilesModal}
|
|
isFileSupported={isFileSupported}
|
|
isOpen={isFilesModalOpen}
|
|
onFileRemove={handleRemoveFileByIndex}
|
|
modalHeight={modalHeight}
|
|
storeFile={storeFile}
|
|
refreshRecentFiles={refreshRecentFiles}
|
|
>
|
|
{isMobile ? <MobileLayout /> : <DesktopLayout />}
|
|
</FileManagerProvider>
|
|
</Dropzone>
|
|
|
|
<DragOverlay isVisible={isDragging} />
|
|
</div>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default FileManager;
|