mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
Replace filemanager with recent files system
This commit is contained in:
parent
61699a08a5
commit
35820e8a24
168
frontend/src/components/shared/FileGrid.tsx
Normal file
168
frontend/src/components/shared/FileGrid.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Flex, Group, Text, Button, TextInput, Select, Badge } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import SortIcon from "@mui/icons-material/Sort";
|
||||
import FileCard from "../fileManagement/FileCard";
|
||||
import { FileWithUrl } from "../../types/file";
|
||||
|
||||
interface FileGridProps {
|
||||
files: FileWithUrl[];
|
||||
onRemove?: (index: number) => void;
|
||||
onDoubleClick?: (file: FileWithUrl) => void;
|
||||
onView?: (file: FileWithUrl) => void;
|
||||
onEdit?: (file: FileWithUrl) => void;
|
||||
onSelect?: (fileId: string) => void;
|
||||
selectedFiles?: string[];
|
||||
showSearch?: boolean;
|
||||
showSort?: boolean;
|
||||
maxDisplay?: number; // If set, shows only this many files with "Show All" option
|
||||
onShowAll?: () => void;
|
||||
showingAll?: boolean;
|
||||
}
|
||||
|
||||
type SortOption = 'date' | 'name' | 'size';
|
||||
|
||||
const FileGrid = ({
|
||||
files,
|
||||
onRemove,
|
||||
onDoubleClick,
|
||||
onView,
|
||||
onEdit,
|
||||
onSelect,
|
||||
selectedFiles = [],
|
||||
showSearch = false,
|
||||
showSort = false,
|
||||
maxDisplay,
|
||||
onShowAll,
|
||||
showingAll = false
|
||||
}: FileGridProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [sortBy, setSortBy] = useState<SortOption>('date');
|
||||
|
||||
// Filter files based on search term
|
||||
const filteredFiles = files.filter(file =>
|
||||
file.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
// Sort files
|
||||
const sortedFiles = [...filteredFiles].sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'date':
|
||||
return (b.lastModified || 0) - (a.lastModified || 0);
|
||||
case 'name':
|
||||
return a.name.localeCompare(b.name);
|
||||
case 'size':
|
||||
return (b.size || 0) - (a.size || 0);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
// Apply max display limit if specified
|
||||
const displayFiles = maxDisplay && !showingAll
|
||||
? sortedFiles.slice(0, maxDisplay)
|
||||
: sortedFiles;
|
||||
|
||||
const hasMoreFiles = maxDisplay && !showingAll && sortedFiles.length > maxDisplay;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* Search and Sort Controls */}
|
||||
{(showSearch || showSort) && (
|
||||
<Group mb="md" justify="space-between" wrap="wrap" gap="sm">
|
||||
{showSearch && (
|
||||
<TextInput
|
||||
placeholder={t("fileManager.searchFiles", "Search files...")}
|
||||
leftSection={<SearchIcon size={16} />}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.currentTarget.value)}
|
||||
style={{ flexGrow: 1, maxWidth: 300, minWidth: 200 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showSort && (
|
||||
<Select
|
||||
data={[
|
||||
{ value: 'date', label: t("fileManager.sortByDate", "Sort by Date") },
|
||||
{ value: 'name', label: t("fileManager.sortByName", "Sort by Name") },
|
||||
{ value: 'size', label: t("fileManager.sortBySize", "Sort by Size") }
|
||||
]}
|
||||
value={sortBy}
|
||||
onChange={(value) => setSortBy(value as SortOption)}
|
||||
leftSection={<SortIcon size={16} />}
|
||||
style={{ minWidth: 150 }}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{/* File Count Badge */}
|
||||
{(showSearch || showSort) && (
|
||||
<Group mb="sm">
|
||||
<Badge variant="light" size="sm">
|
||||
{displayFiles.length} {displayFiles.length === 1 ? 'file' : 'files'}
|
||||
{hasMoreFiles && ` (${sortedFiles.length - maxDisplay!} more)`}
|
||||
</Badge>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{/* Files Grid */}
|
||||
<Flex
|
||||
wrap="wrap"
|
||||
gap="lg"
|
||||
justify="flex-start"
|
||||
style={{
|
||||
width: "100%",
|
||||
// Responsive grid spacing
|
||||
'@media (max-width: 768px)': {
|
||||
gap: 'md'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{displayFiles.map((file, idx) => {
|
||||
const originalIdx = files.findIndex(f => (f.id || f.name) === (file.id || file.name));
|
||||
return (
|
||||
<FileCard
|
||||
key={file.id || file.name + idx}
|
||||
file={file}
|
||||
onRemove={onRemove ? () => onRemove(originalIdx) : undefined}
|
||||
onDoubleClick={onDoubleClick ? () => onDoubleClick(file) : undefined}
|
||||
onView={onView ? () => onView(file) : undefined}
|
||||
onEdit={onEdit ? () => onEdit(file) : undefined}
|
||||
isSelected={selectedFiles.includes(file.id || file.name)}
|
||||
onSelect={onSelect ? () => onSelect(file.id || file.name) : undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
|
||||
{/* Show All Button */}
|
||||
{hasMoreFiles && onShowAll && (
|
||||
<Group justify="center" mt="md">
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={onShowAll}
|
||||
>
|
||||
{t("fileManager.showAll", "Show All")} ({sortedFiles.length} files)
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{/* Empty State */}
|
||||
{displayFiles.length === 0 && (
|
||||
<Box style={{ textAlign: 'center', padding: '2rem' }}>
|
||||
<Text c="dimmed">
|
||||
{searchTerm
|
||||
? t("fileManager.noFilesFound", "No files found matching your search")
|
||||
: t("fileManager.noFiles", "No files available")
|
||||
}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileGrid;
|
@ -1,9 +1,12 @@
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { Stack, Button, Text, Center } from '@mantine/core';
|
||||
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { Stack, Button, Text, Center, Box, Divider } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FilePickerModal from './FilePickerModal';
|
||||
import { fileStorage } from '../../services/fileStorage';
|
||||
import { FileWithUrl } from '../../types/file';
|
||||
import FileGrid from './FileGrid';
|
||||
import MultiSelectControls from './MultiSelectControls';
|
||||
|
||||
interface FileUploadSelectorProps {
|
||||
// Appearance
|
||||
@ -20,6 +23,10 @@ interface FileUploadSelectorProps {
|
||||
// Loading state
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
|
||||
// Recent files
|
||||
showRecentFiles?: boolean;
|
||||
maxRecentFiles?: number;
|
||||
}
|
||||
|
||||
const FileUploadSelector = ({
|
||||
@ -32,20 +39,40 @@ const FileUploadSelector = ({
|
||||
accept = ["application/pdf"],
|
||||
loading = false,
|
||||
disabled = false,
|
||||
showRecentFiles = true,
|
||||
maxRecentFiles = 8,
|
||||
}: FileUploadSelectorProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showFilePickerModal, setShowFilePickerModal] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFileUpload = useCallback((uploadedFiles: File[]) => {
|
||||
// Recent files state
|
||||
const [recentFiles, setRecentFiles] = useState<FileWithUrl[]>([]);
|
||||
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
||||
const [showingAllRecent, setShowingAllRecent] = useState(false);
|
||||
const [recentFilesLoading, setRecentFilesLoading] = useState(false);
|
||||
|
||||
const handleFileUpload = useCallback(async (uploadedFiles: File[]) => {
|
||||
if (uploadedFiles.length === 0) return;
|
||||
|
||||
// Auto-save uploaded files to recent files
|
||||
if (showRecentFiles) {
|
||||
try {
|
||||
for (const file of uploadedFiles) {
|
||||
await fileStorage.storeFile(file);
|
||||
}
|
||||
// Refresh recent files list
|
||||
loadRecentFiles();
|
||||
} catch (error) {
|
||||
console.error('Failed to save files to recent:', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (onFilesSelect) {
|
||||
onFilesSelect(uploadedFiles);
|
||||
} else if (onFileSelect) {
|
||||
onFileSelect(uploadedFiles[0]);
|
||||
}
|
||||
}, [onFileSelect, onFilesSelect]);
|
||||
}, [onFileSelect, onFilesSelect, showRecentFiles]);
|
||||
|
||||
const handleFileInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = event.target.files;
|
||||
@ -64,23 +91,98 @@ const FileUploadSelector = ({
|
||||
fileInputRef.current?.click();
|
||||
}, []);
|
||||
|
||||
const handleStorageSelection = useCallback((selectedFiles: File[]) => {
|
||||
if (selectedFiles.length === 0) return;
|
||||
// Load recent files from storage
|
||||
const loadRecentFiles = useCallback(async () => {
|
||||
if (!showRecentFiles) return;
|
||||
|
||||
if (onFilesSelect) {
|
||||
onFilesSelect(selectedFiles);
|
||||
} else if (onFileSelect) {
|
||||
onFileSelect(selectedFiles[0]);
|
||||
setRecentFilesLoading(true);
|
||||
try {
|
||||
const files = await fileStorage.getAllFiles();
|
||||
// Sort by last modified date (newest first)
|
||||
const sortedFiles = files.sort((a, b) => (b.lastModified || 0) - (a.lastModified || 0));
|
||||
setRecentFiles(sortedFiles);
|
||||
} catch (error) {
|
||||
console.error('Failed to load recent files:', error);
|
||||
setRecentFiles([]);
|
||||
} finally {
|
||||
setRecentFilesLoading(false);
|
||||
}
|
||||
}, [showRecentFiles]);
|
||||
|
||||
// Convert FileWithUrl to File for upload
|
||||
const convertToFile = async (fileWithUrl: FileWithUrl): Promise<File> => {
|
||||
if (fileWithUrl.url && fileWithUrl.url.startsWith('blob:')) {
|
||||
const response = await fetch(fileWithUrl.url);
|
||||
const data = await response.arrayBuffer();
|
||||
return new File([data], fileWithUrl.name, {
|
||||
type: fileWithUrl.type || 'application/pdf',
|
||||
lastModified: fileWithUrl.lastModified || Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
// Load from IndexedDB
|
||||
const storedFile = await fileStorage.getFile(fileWithUrl.id || fileWithUrl.name);
|
||||
if (storedFile) {
|
||||
return new File([storedFile.data], storedFile.name, {
|
||||
type: storedFile.type,
|
||||
lastModified: storedFile.lastModified
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error('File not found in storage');
|
||||
};
|
||||
|
||||
const handleRecentFileSelection = useCallback(async (file: FileWithUrl) => {
|
||||
try {
|
||||
const fileObj = await convertToFile(file);
|
||||
if (onFilesSelect) {
|
||||
onFilesSelect([fileObj]);
|
||||
} else if (onFileSelect) {
|
||||
onFileSelect(fileObj);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load file from recent:', error);
|
||||
}
|
||||
}, [onFileSelect, onFilesSelect]);
|
||||
|
||||
const handleSelectedRecentFiles = useCallback(async () => {
|
||||
if (selectedFiles.length === 0) return;
|
||||
|
||||
try {
|
||||
const selectedFileObjects = recentFiles.filter(f => selectedFiles.includes(f.id || f.name));
|
||||
const filePromises = selectedFileObjects.map(convertToFile);
|
||||
const files = await Promise.all(filePromises);
|
||||
|
||||
if (onFilesSelect) {
|
||||
onFilesSelect(files);
|
||||
}
|
||||
|
||||
setSelectedFiles([]);
|
||||
} catch (error) {
|
||||
console.error('Failed to load selected files:', error);
|
||||
}
|
||||
}, [selectedFiles, recentFiles, onFilesSelect]);
|
||||
|
||||
const toggleFileSelection = useCallback((fileId: string) => {
|
||||
setSelectedFiles(prev =>
|
||||
prev.includes(fileId)
|
||||
? prev.filter(id => id !== fileId)
|
||||
: [...prev, fileId]
|
||||
);
|
||||
}, []);
|
||||
|
||||
// Load recent files on mount
|
||||
useEffect(() => {
|
||||
loadRecentFiles();
|
||||
}, [loadRecentFiles]);
|
||||
|
||||
// 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");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack align="center" gap="xl">
|
||||
<Stack align="center" gap="sm">
|
||||
{/* Title and description */}
|
||||
<Stack align="center" gap="md">
|
||||
<UploadFileIcon style={{ fontSize: 64 }} />
|
||||
@ -94,19 +196,6 @@ const FileUploadSelector = ({
|
||||
|
||||
{/* Action buttons */}
|
||||
<Stack align="center" gap="md" w="100%">
|
||||
<Button
|
||||
variant="filled"
|
||||
size="lg"
|
||||
onClick={() => setShowFilePickerModal(true)}
|
||||
disabled={disabled || sharedFiles.length === 0}
|
||||
loading={loading}
|
||||
>
|
||||
{loading ? "Loading..." : `Load from Storage (${sharedFiles.length} files available)`}
|
||||
</Button>
|
||||
|
||||
<Text size="md" c="dimmed">
|
||||
{t("fileUpload.or", "or")}
|
||||
</Text>
|
||||
|
||||
{showDropzone ? (
|
||||
<Dropzone
|
||||
@ -114,7 +203,7 @@ const FileUploadSelector = ({
|
||||
accept={accept}
|
||||
multiple={true}
|
||||
disabled={disabled || loading}
|
||||
style={{ width: '100%', minHeight: 120 }}
|
||||
style={{ width: '100%', height: "5rem" }}
|
||||
activateOnClick={true}
|
||||
>
|
||||
<Center>
|
||||
@ -142,7 +231,7 @@ const FileUploadSelector = ({
|
||||
>
|
||||
{t("fileUpload.uploadFiles", "Upload Files")}
|
||||
</Button>
|
||||
|
||||
|
||||
{/* Manual file input as backup */}
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
@ -155,15 +244,45 @@ const FileUploadSelector = ({
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* File Picker Modal */}
|
||||
<FilePickerModal
|
||||
opened={showFilePickerModal}
|
||||
onClose={() => setShowFilePickerModal(false)}
|
||||
storedFiles={sharedFiles}
|
||||
onSelectFiles={handleStorageSelection}
|
||||
/>
|
||||
{/* Recent Files Section */}
|
||||
{showRecentFiles && recentFiles.length > 0 && (
|
||||
<Box w="100%" >
|
||||
<Divider my="md" />
|
||||
<Text size="lg" fw={500} mb="md">
|
||||
{t("fileUpload.recentFiles", "Recent Files")}
|
||||
</Text>
|
||||
<MultiSelectControls
|
||||
selectedCount={selectedFiles.length}
|
||||
onClearSelection={() => setSelectedFiles([])}
|
||||
onAddToUpload={handleSelectedRecentFiles}
|
||||
/>
|
||||
|
||||
<FileGrid
|
||||
files={recentFiles}
|
||||
onDoubleClick={handleRecentFileSelection}
|
||||
onSelect={toggleFileSelection}
|
||||
selectedFiles={selectedFiles}
|
||||
maxDisplay={showingAllRecent ? undefined : maxRecentFiles}
|
||||
onShowAll={() => setShowingAllRecent(true)}
|
||||
showingAll={showingAllRecent}
|
||||
showSearch={showingAllRecent}
|
||||
showSort={showingAllRecent}
|
||||
/>
|
||||
|
||||
{showingAllRecent && (
|
||||
<Center mt="md">
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={() => setShowingAllRecent(false)}
|
||||
>
|
||||
{t("fileUpload.showLess", "Show Less")}
|
||||
</Button>
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
76
frontend/src/components/shared/MultiSelectControls.tsx
Normal file
76
frontend/src/components/shared/MultiSelectControls.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import React from "react";
|
||||
import { Box, Group, Text, Button } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface MultiSelectControlsProps {
|
||||
selectedCount: number;
|
||||
onClearSelection: () => void;
|
||||
onOpenInFileEditor?: () => void;
|
||||
onOpenInPageEditor?: () => void;
|
||||
onAddToUpload?: () => void; // New action for recent files
|
||||
}
|
||||
|
||||
const MultiSelectControls = ({
|
||||
selectedCount,
|
||||
onClearSelection,
|
||||
onOpenInFileEditor,
|
||||
onOpenInPageEditor,
|
||||
onAddToUpload
|
||||
}: MultiSelectControlsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (selectedCount === 0) return null;
|
||||
|
||||
return (
|
||||
<Box mb="md" p="md" style={{ backgroundColor: 'var(--mantine-color-blue-0)', borderRadius: 8 }}>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm">
|
||||
{selectedCount} {t("fileManager.filesSelected", "files selected")}
|
||||
</Text>
|
||||
<Group>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={onClearSelection}
|
||||
>
|
||||
{t("fileManager.clearSelection", "Clear Selection")}
|
||||
</Button>
|
||||
|
||||
{onAddToUpload && (
|
||||
<Button
|
||||
size="xs"
|
||||
color="green"
|
||||
onClick={onAddToUpload}
|
||||
>
|
||||
{t("fileManager.addToUpload", "Add to Upload")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onOpenInFileEditor && (
|
||||
<Button
|
||||
size="xs"
|
||||
color="orange"
|
||||
onClick={onOpenInFileEditor}
|
||||
disabled={selectedCount === 0}
|
||||
>
|
||||
{t("fileManager.openInFileEditor", "Open in File Editor")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onOpenInPageEditor && (
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
onClick={onOpenInPageEditor}
|
||||
disabled={selectedCount === 0}
|
||||
>
|
||||
{t("fileManager.openInPageEditor", "Open in Page Editor")}
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiSelectControls;
|
@ -8,7 +8,6 @@ import LightModeIcon from '@mui/icons-material/LightMode';
|
||||
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import EditNoteIcon from "@mui/icons-material/EditNote";
|
||||
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
||||
import FolderIcon from "@mui/icons-material/Folder";
|
||||
import { Group } from "@mantine/core";
|
||||
|
||||
@ -29,14 +28,6 @@ const VIEW_OPTIONS = [
|
||||
),
|
||||
value: "pageEditor",
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Group gap={4}>
|
||||
<InsertDriveFileIcon fontSize="small" />
|
||||
</Group>
|
||||
),
|
||||
value: "fileManager",
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Group gap={4}>
|
||||
|
@ -13,7 +13,6 @@ import rainbowStyles from '../styles/rainbow.module.css';
|
||||
|
||||
import ToolPicker from "../components/tools/ToolPicker";
|
||||
import TopControls from "../components/shared/TopControls";
|
||||
import FileManager from "../components/fileManagement/FileManager";
|
||||
import FileEditor from "../components/pageEditor/FileEditor";
|
||||
import PageEditor from "../components/pageEditor/PageEditor";
|
||||
import PageEditorControls from "../components/pageEditor/PageEditorControls";
|
||||
@ -40,7 +39,7 @@ type ToolRegistry = {
|
||||
const baseToolRegistry = {
|
||||
split: { icon: <ContentCutIcon />, component: SplitPdfPanel, view: "viewer" },
|
||||
compress: { icon: <ZoomInMapIcon />, component: CompressPdfPanel, view: "viewer" },
|
||||
merge: { icon: <AddToPhotosIcon />, component: MergePdfPanel, view: "fileManager" },
|
||||
merge: { icon: <AddToPhotosIcon />, component: MergePdfPanel, view: "pageEditor" },
|
||||
};
|
||||
|
||||
export default function HomePage() {
|
||||
@ -376,16 +375,7 @@ export default function HomePage() {
|
||||
/>
|
||||
{/* Main content area */}
|
||||
<Box className="flex-1 min-h-0 margin-top-200 relative z-10">
|
||||
{currentView === "fileManager" ? (
|
||||
<FileManager
|
||||
files={storedFiles}
|
||||
setFiles={setStoredFiles}
|
||||
setCurrentView={handleViewChange}
|
||||
onOpenFileEditor={handleOpenFileEditor}
|
||||
onOpenPageEditor={handleOpenPageEditor}
|
||||
onLoadFileToActive={addToActiveFiles}
|
||||
/>
|
||||
) : (currentView != "fileManager") && !activeFiles[0] ? (
|
||||
{!activeFiles[0] ? (
|
||||
<Container size="lg" p="xl" h="100%" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<FileUploadSelector
|
||||
title={currentView === "viewer"
|
||||
@ -397,9 +387,13 @@ export default function HomePage() {
|
||||
onFileSelect={(file) => {
|
||||
addToActiveFiles(file);
|
||||
}}
|
||||
allowMultiple={false}
|
||||
onFilesSelect={(files) => {
|
||||
files.forEach(addToActiveFiles);
|
||||
}}
|
||||
accept={["application/pdf"]}
|
||||
loading={false}
|
||||
showRecentFiles={true}
|
||||
maxRecentFiles={8}
|
||||
/>
|
||||
</Container>
|
||||
) : currentView === "fileEditor" ? (
|
||||
|
Loading…
Reference in New Issue
Block a user