mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-07-29 00:25:28 +00:00
Clean up
This commit is contained in:
parent
cbc5616a39
commit
25e9db2570
@ -1653,6 +1653,17 @@
|
|||||||
"uploadError": "Failed to upload some files.",
|
"uploadError": "Failed to upload some files.",
|
||||||
"failedToOpen": "Failed to open file. It may have been removed from storage.",
|
"failedToOpen": "Failed to open file. It may have been removed from storage.",
|
||||||
"failedToLoad": "Failed to load file to active set.",
|
"failedToLoad": "Failed to load file to active set.",
|
||||||
"storageCleared": "Browser cleared storage. Files have been removed. Please re-upload."
|
"storageCleared": "Browser cleared storage. Files have been removed. Please re-upload.",
|
||||||
|
"clearAll": "Clear All",
|
||||||
|
"reloadFiles": "Reload Files"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"temporaryNotice": "Files are stored temporarily in your browser and may be cleared automatically",
|
||||||
|
"storageLimit": "Storage limit",
|
||||||
|
"storageUsed": "Temporary Storage used",
|
||||||
|
"storageFull": "Storage is nearly full. Consider removing some files.",
|
||||||
|
"fileTooLarge": "File too large. Maximum size per file is",
|
||||||
|
"storageQuotaExceeded": "Storage quota exceeded. Please remove some files before uploading more.",
|
||||||
|
"approximateSize": "Approximate size"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,10 @@ const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, o
|
|||||||
radius="md"
|
radius="md"
|
||||||
withBorder
|
withBorder
|
||||||
p="xs"
|
p="xs"
|
||||||
style={{
|
style={{
|
||||||
width: 225,
|
width: 225,
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
maxWidth: 260,
|
maxWidth: 260,
|
||||||
cursor: onDoubleClick ? "pointer" : undefined,
|
cursor: onDoubleClick ? "pointer" : undefined,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
border: isSelected ? '2px solid var(--mantine-color-blue-6)' : undefined,
|
border: isSelected ? '2px solid var(--mantine-color-blue-6)' : undefined,
|
||||||
@ -109,25 +109,25 @@ const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, o
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{thumb ? (
|
{thumb ? (
|
||||||
<Image
|
<Image
|
||||||
src={thumb}
|
src={thumb}
|
||||||
alt="PDF thumbnail"
|
alt="PDF thumbnail"
|
||||||
height={110}
|
height={110}
|
||||||
width={80}
|
width={80}
|
||||||
fit="contain"
|
fit="contain"
|
||||||
radius="sm"
|
radius="sm"
|
||||||
/>
|
/>
|
||||||
) : isGenerating ? (
|
) : isGenerating ? (
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
border: '2px solid #ddd',
|
border: '2px solid #ddd',
|
||||||
borderTop: '2px solid #666',
|
borderTop: '2px solid #666',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
animation: 'spin 1s linear infinite',
|
animation: 'spin 1s linear infinite',
|
||||||
@ -136,11 +136,11 @@ const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, o
|
|||||||
<Text size="xs" c="dimmed">Generating...</Text>
|
<Text size="xs" c="dimmed">Generating...</Text>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
}}>
|
}}>
|
||||||
<ThemeIcon
|
<ThemeIcon
|
||||||
variant="light"
|
variant="light"
|
||||||
@ -157,30 +157,30 @@ const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, o
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Text fw={500} size="sm" lineClamp={1} ta="center">
|
<Text fw={500} size="sm" lineClamp={1} ta="center">
|
||||||
{file.name}
|
{file.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Group gap="xs" justify="center">
|
<Group gap="xs" justify="center">
|
||||||
<Badge color="gray" variant="light" size="sm">
|
<Badge color="red" variant="light" size="sm">
|
||||||
{getFileSize(file)}
|
{getFileSize(file)}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge color="blue" variant="light" size="sm">
|
<Badge color="blue" variant="light" size="sm">
|
||||||
{getFileDate(file)}
|
{getFileDate(file)}
|
||||||
</Badge>
|
</Badge>
|
||||||
{file.storedInIndexedDB && (
|
{file.storedInIndexedDB && (
|
||||||
<Badge
|
<Badge
|
||||||
color="green"
|
color="green"
|
||||||
variant="light"
|
variant="light"
|
||||||
size="sm"
|
size="sm"
|
||||||
leftSection={<StorageIcon style={{ fontSize: 12 }} />}
|
leftSection={<StorageIcon style={{ fontSize: 12 }} />}
|
||||||
>
|
>
|
||||||
DB
|
DB
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
size="xs"
|
size="xs"
|
||||||
@ -198,4 +198,4 @@ const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, o
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FileCard;
|
export default FileCard;
|
||||||
|
@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { GlobalWorkerOptions } from "pdfjs-dist";
|
import { GlobalWorkerOptions } from "pdfjs-dist";
|
||||||
import { StorageStats } from "../../services/fileStorage";
|
import { StorageStats } from "../../services/fileStorage";
|
||||||
import { FileWithUrl, defaultStorageConfig } from "../../types/file";
|
import { FileWithUrl, defaultStorageConfig, initializeStorageConfig, StorageConfig } from "../../types/file";
|
||||||
|
|
||||||
// Refactored imports
|
// Refactored imports
|
||||||
import { fileOperationsService } from "../../services/fileOperationsService";
|
import { fileOperationsService } from "../../services/fileOperationsService";
|
||||||
@ -39,6 +39,7 @@ const FileManager = ({
|
|||||||
const [notification, setNotification] = useState<string | null>(null);
|
const [notification, setNotification] = useState<string | null>(null);
|
||||||
const [filesLoaded, setFilesLoaded] = useState(false);
|
const [filesLoaded, setFilesLoaded] = useState(false);
|
||||||
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
||||||
|
const [storageConfig, setStorageConfig] = useState<StorageConfig>(defaultStorageConfig);
|
||||||
|
|
||||||
// Extract operations from service for cleaner code
|
// Extract operations from service for cleaner code
|
||||||
const {
|
const {
|
||||||
@ -75,6 +76,21 @@ const FileManager = ({
|
|||||||
}
|
}
|
||||||
}, [filesLoaded]);
|
}, [filesLoaded]);
|
||||||
|
|
||||||
|
// Initialize storage configuration on mount
|
||||||
|
useEffect(() => {
|
||||||
|
const initStorage = async () => {
|
||||||
|
try {
|
||||||
|
const config = await initializeStorageConfig();
|
||||||
|
setStorageConfig(config);
|
||||||
|
console.log('Initialized storage config:', config);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to initialize storage config, using defaults:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initStorage();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Load storage stats and set up periodic updates
|
// Load storage stats and set up periodic updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleLoadStorageStats();
|
handleLoadStorageStats();
|
||||||
@ -143,11 +159,47 @@ const FileManager = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateStorageLimits = (filesToUpload: File[]): { valid: boolean; error?: string } => {
|
||||||
|
// Check individual file sizes
|
||||||
|
for (const file of filesToUpload) {
|
||||||
|
if (file.size > storageConfig.maxFileSize) {
|
||||||
|
const maxSizeMB = Math.round(storageConfig.maxFileSize / (1024 * 1024));
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error: `${t("storage.fileTooLarge", "File too large. Maximum size per file is")} ${maxSizeMB}MB`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check total storage capacity
|
||||||
|
if (storageStats) {
|
||||||
|
const totalNewSize = filesToUpload.reduce((sum, file) => sum + file.size, 0);
|
||||||
|
const projectedUsage = storageStats.totalSize + totalNewSize;
|
||||||
|
|
||||||
|
if (projectedUsage > storageConfig.maxTotalStorage) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error: t("storage.storageQuotaExceeded", "Storage quota exceeded. Please remove some files before uploading more.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true };
|
||||||
|
};
|
||||||
|
|
||||||
const handleDrop = async (uploadedFiles: File[]) => {
|
const handleDrop = async (uploadedFiles: File[]) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newFiles = await uploadFiles(uploadedFiles, defaultStorageConfig.useIndexedDB);
|
// Validate storage limits before uploading
|
||||||
|
const validation = validateStorageLimits(uploadedFiles);
|
||||||
|
if (!validation.valid) {
|
||||||
|
setNotification(validation.error);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFiles = await uploadFiles(uploadedFiles, storageConfig.useIndexedDB);
|
||||||
|
|
||||||
// Update files state
|
// Update files state
|
||||||
setFiles((prevFiles) => (allowMultiple ? [...prevFiles, ...newFiles] : newFiles));
|
setFiles((prevFiles) => (allowMultiple ? [...prevFiles, ...newFiles] : newFiles));
|
||||||
@ -269,8 +321,8 @@ const FileManager = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggleFileSelection = (fileId: string) => {
|
const toggleFileSelection = (fileId: string) => {
|
||||||
setSelectedFiles(prev =>
|
setSelectedFiles(prev =>
|
||||||
prev.includes(fileId)
|
prev.includes(fileId)
|
||||||
? prev.filter(id => id !== fileId)
|
? prev.filter(id => id !== fileId)
|
||||||
: [...prev, fileId]
|
: [...prev, fileId]
|
||||||
);
|
);
|
||||||
@ -286,12 +338,11 @@ const FileManager = ({
|
|||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
margin: "0 auto",
|
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: "20px"
|
paddingTop: "3rem"
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
{/* File upload is now handled by FileUploadSelector when no files exist */}
|
{/* File upload is now handled by FileUploadSelector when no files exist */}
|
||||||
@ -302,6 +353,7 @@ const FileManager = ({
|
|||||||
filesCount={files.length}
|
filesCount={files.length}
|
||||||
onClearAll={handleClearAll}
|
onClearAll={handleClearAll}
|
||||||
onReloadFiles={handleReloadFiles}
|
onReloadFiles={handleReloadFiles}
|
||||||
|
storageConfig={storageConfig}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Multi-selection controls */}
|
{/* Multi-selection controls */}
|
||||||
@ -312,16 +364,16 @@ const FileManager = ({
|
|||||||
{selectedFiles.length} {t("fileManager.filesSelected", "files selected")}
|
{selectedFiles.length} {t("fileManager.filesSelected", "files selected")}
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => setSelectedFiles([])}
|
onClick={() => setSelectedFiles([])}
|
||||||
>
|
>
|
||||||
{t("fileManager.clearSelection", "Clear Selection")}
|
{t("fileManager.clearSelection", "Clear Selection")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
color="orange"
|
color="orange"
|
||||||
onClick={handleOpenSelectedInEditor}
|
onClick={handleOpenSelectedInEditor}
|
||||||
disabled={selectedFiles.length === 0}
|
disabled={selectedFiles.length === 0}
|
||||||
>
|
>
|
||||||
@ -332,31 +384,12 @@ const FileManager = ({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Files Display */}
|
|
||||||
{files.length === 0 ? (
|
|
||||||
<FileUploadSelector
|
|
||||||
title={t("fileManager.title", "Upload PDF Files")}
|
|
||||||
subtitle={t("fileManager.subtitle", "Add files to your storage for easy access across tools")}
|
|
||||||
sharedFiles={[]} // FileManager is the source, so no shared files
|
|
||||||
onFilesSelect={(uploadedFiles) => {
|
|
||||||
// Handle multiple files - add to storage AND active set
|
|
||||||
handleDrop(uploadedFiles);
|
|
||||||
if (onLoadFileToActive && uploadedFiles.length > 0) {
|
|
||||||
uploadedFiles.forEach(onLoadFileToActive);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
allowMultiple={allowMultiple}
|
|
||||||
accept={["application/pdf"]}
|
|
||||||
loading={loading}
|
|
||||||
showDropzone={true}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box>
|
|
||||||
<Flex
|
<Flex
|
||||||
wrap="wrap"
|
wrap="wrap"
|
||||||
gap="lg"
|
gap="lg"
|
||||||
justify="flex-start"
|
justify="flex-start"
|
||||||
style={{ width: "fit-content", margin: "0 auto" }}
|
style={{ width: "90%", marginTop: "1rem"}}
|
||||||
>
|
>
|
||||||
{files.map((file, idx) => (
|
{files.map((file, idx) => (
|
||||||
<FileCard
|
<FileCard
|
||||||
@ -371,8 +404,7 @@ const FileManager = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Notifications */}
|
{/* Notifications */}
|
||||||
{notification && (
|
{notification && (
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Card, Group, Text, Button, Progress } from "@mantine/core";
|
import { Card, Group, Text, Button, Progress, Alert, Stack } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import StorageIcon from "@mui/icons-material/Storage";
|
import StorageIcon from "@mui/icons-material/Storage";
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import WarningIcon from "@mui/icons-material/Warning";
|
||||||
import { StorageStats } from "../../services/fileStorage";
|
import { StorageStats } from "../../services/fileStorage";
|
||||||
import { formatFileSize } from "../../utils/fileUtils";
|
import { formatFileSize } from "../../utils/fileUtils";
|
||||||
import { getStorageUsagePercent } from "../../utils/storageUtils";
|
import { getStorageUsagePercent } from "../../utils/storageUtils";
|
||||||
|
import { StorageConfig } from "../../types/file";
|
||||||
|
|
||||||
interface StorageStatsCardProps {
|
interface StorageStatsCardProps {
|
||||||
storageStats: StorageStats | null;
|
storageStats: StorageStats | null;
|
||||||
filesCount: number;
|
filesCount: number;
|
||||||
onClearAll: () => void;
|
onClearAll: () => void;
|
||||||
onReloadFiles: () => void;
|
onReloadFiles: () => void;
|
||||||
|
storageConfig: StorageConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StorageStatsCard = ({
|
const StorageStatsCard = ({
|
||||||
@ -19,58 +22,71 @@ const StorageStatsCard = ({
|
|||||||
filesCount,
|
filesCount,
|
||||||
onClearAll,
|
onClearAll,
|
||||||
onReloadFiles,
|
onReloadFiles,
|
||||||
|
storageConfig,
|
||||||
}: StorageStatsCardProps) => {
|
}: StorageStatsCardProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!storageStats) return null;
|
if (!storageStats) return null;
|
||||||
|
|
||||||
const storageUsagePercent = getStorageUsagePercent(storageStats);
|
const storageUsagePercent = getStorageUsagePercent(storageStats);
|
||||||
|
const totalUsed = storageStats.totalSize || storageStats.used;
|
||||||
|
const hardLimitPercent = (totalUsed / storageConfig.maxTotalStorage) * 100;
|
||||||
|
const isNearLimit = hardLimitPercent >= storageConfig.warningThreshold * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card withBorder p="sm" mb="md" style={{ width: "90%", maxWidth: 600 }}>
|
<Stack gap="sm" style={{ width: "90%", maxWidth: 600 }}>
|
||||||
<Group align="center" gap="md">
|
<Card withBorder p="sm">
|
||||||
<StorageIcon />
|
<Group align="center" gap="md">
|
||||||
<div style={{ flex: 1 }}>
|
<StorageIcon />
|
||||||
<Text size="sm" fw={500}>
|
<div style={{ flex: 1 }}>
|
||||||
{t("fileManager.storage", "Storage")}: {formatFileSize(storageStats.used)}
|
<Text size="sm" fw={500}>
|
||||||
{storageStats.quota && ` / ${formatFileSize(storageStats.quota)}`}
|
{t("storage.storageUsed", "Storage used")}: {formatFileSize(totalUsed)} / {formatFileSize(storageConfig.maxTotalStorage)}
|
||||||
</Text>
|
</Text>
|
||||||
{storageStats.quota && (
|
|
||||||
<Progress
|
<Progress
|
||||||
value={storageUsagePercent}
|
value={hardLimitPercent}
|
||||||
color={storageUsagePercent > 80 ? "red" : storageUsagePercent > 60 ? "yellow" : "blue"}
|
color={isNearLimit ? "red" : hardLimitPercent > 60 ? "yellow" : "blue"}
|
||||||
size="sm"
|
size="sm"
|
||||||
mt={4}
|
mt={4}
|
||||||
/>
|
/>
|
||||||
)}
|
<Group justify="space-between" mt={2}>
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
{storageStats.fileCount} {t("fileManager.filesStored", "files stored")}
|
{storageStats.fileCount} files • {t("storage.approximateSize", "Approximate size")}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
<Text size="xs" c={isNearLimit ? "red" : "dimmed"}>
|
||||||
<Group gap="xs">
|
{Math.round(hardLimitPercent)}% used
|
||||||
{filesCount > 0 && (
|
</Text>
|
||||||
|
</Group>
|
||||||
|
{isNearLimit && (
|
||||||
|
<Text size="xs" c="red" mt={4}>
|
||||||
|
{t("storage.storageFull", "Storage is nearly full. Consider removing some files.")}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Group gap="xs">
|
||||||
|
{filesCount > 0 && (
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="xs"
|
||||||
|
onClick={onClearAll}
|
||||||
|
leftSection={<DeleteIcon style={{ fontSize: 16 }} />}
|
||||||
|
>
|
||||||
|
{t("fileManager.clearAll", "Clear All")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="red"
|
color="blue"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={onClearAll}
|
onClick={onReloadFiles}
|
||||||
leftSection={<DeleteIcon style={{ fontSize: 16 }} />}
|
|
||||||
>
|
>
|
||||||
{t("fileManager.clearAll", "Clear All")}
|
{t("fileManager.reloadFiles", "Reload Files")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</Group>
|
||||||
<Button
|
|
||||||
variant="light"
|
|
||||||
color="blue"
|
|
||||||
size="xs"
|
|
||||||
onClick={onReloadFiles}
|
|
||||||
>
|
|
||||||
Reload Files
|
|
||||||
</Button>
|
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Card>
|
||||||
</Card>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StorageStatsCard;
|
export default StorageStatsCard;
|
||||||
|
@ -59,7 +59,7 @@ const FileUploadSelector = ({
|
|||||||
}, [allowMultiple, onFileSelect, onFilesSelect]);
|
}, [allowMultiple, onFileSelect, onFilesSelect]);
|
||||||
|
|
||||||
// Get default title and subtitle from translations if not provided
|
// Get default title and subtitle from translations if not provided
|
||||||
const displayTitle = title || t(allowMultiple ? "fileUpload.selectFiles" : "fileUpload.selectFile",
|
const displayTitle = title || t(allowMultiple ? "fileUpload.selectFiles" : "fileUpload.selectFile",
|
||||||
allowMultiple ? "Select files" : "Select a file");
|
allowMultiple ? "Select files" : "Select a file");
|
||||||
const displaySubtitle = subtitle || t(allowMultiple ? "fileUpload.chooseFromStorageMultiple" : "fileUpload.chooseFromStorage",
|
const displaySubtitle = subtitle || t(allowMultiple ? "fileUpload.chooseFromStorageMultiple" : "fileUpload.chooseFromStorage",
|
||||||
allowMultiple ? "Choose files from storage or upload new PDFs" : "Choose a file from storage or upload a new PDF");
|
allowMultiple ? "Choose files from storage or upload new PDFs" : "Choose a file from storage or upload a new PDF");
|
||||||
@ -87,10 +87,7 @@ const FileUploadSelector = ({
|
|||||||
disabled={disabled || sharedFiles.length === 0}
|
disabled={disabled || sharedFiles.length === 0}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
{loading
|
{loading ? "Loading..." : `Load from Storage (${sharedFiles.length} files available)`}
|
||||||
? t("fileUpload.loading", "Loading...")
|
|
||||||
: `${t("fileUpload.loadFromStorage", "Load from Storage")} (${sharedFiles.length} ${t("fileUpload.filesAvailable", "files available")})`
|
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Text size="md" c="dimmed">
|
<Text size="md" c="dimmed">
|
||||||
@ -112,7 +109,7 @@ const FileUploadSelector = ({
|
|||||||
allowMultiple ? "Drop files here or click to upload" : "Drop file here or click to upload")}
|
allowMultiple ? "Drop files here or click to upload" : "Drop file here or click to upload")}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{accept.includes('application/pdf')
|
{accept.includes('application/pdf')
|
||||||
? t("fileUpload.pdfFilesOnly", "PDF files only")
|
? t("fileUpload.pdfFilesOnly", "PDF files only")
|
||||||
: t("fileUpload.supportedFileTypes", "Supported file types")
|
: t("fileUpload.supportedFileTypes", "Supported file types")
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
/* Import minimal theme variables */
|
/* Import minimal theme variables */
|
||||||
@import './theme.css';
|
@import './theme.css';
|
||||||
|
|
||||||
@tailwind base;
|
@layer base {
|
||||||
@tailwind components;
|
@tailwind base;
|
||||||
@tailwind utilities;
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
@tailwind components;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
@tailwind utilities;
|
||||||
|
}
|
||||||
|
@ -30,7 +30,7 @@ const gray: MantineColorsTuple = [
|
|||||||
export const mantineTheme = createTheme({
|
export const mantineTheme = createTheme({
|
||||||
// Primary color
|
// Primary color
|
||||||
primaryColor: 'primary',
|
primaryColor: 'primary',
|
||||||
|
|
||||||
// Color palette
|
// Color palette
|
||||||
colors: {
|
colors: {
|
||||||
primary,
|
primary,
|
||||||
@ -245,7 +245,7 @@ export const mantineTheme = createTheme({
|
|||||||
},
|
},
|
||||||
control: {
|
control: {
|
||||||
color: 'var(--text-secondary)',
|
color: 'var(--text-secondary)',
|
||||||
'&[data-active]': {
|
'[dataActive]': {
|
||||||
backgroundColor: 'var(--bg-surface)',
|
backgroundColor: 'var(--bg-surface)',
|
||||||
color: 'var(--text-primary)',
|
color: 'var(--text-primary)',
|
||||||
boxShadow: 'var(--shadow-sm)',
|
boxShadow: 'var(--shadow-sm)',
|
||||||
@ -261,7 +261,7 @@ export const mantineTheme = createTheme({
|
|||||||
'*': {
|
'*': {
|
||||||
transition: 'background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease',
|
transition: 'background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Custom scrollbar styling
|
// Custom scrollbar styling
|
||||||
'*::-webkit-scrollbar': {
|
'*::-webkit-scrollbar': {
|
||||||
width: '8px',
|
width: '8px',
|
||||||
@ -278,4 +278,4 @@ export const mantineTheme = createTheme({
|
|||||||
backgroundColor: 'var(--color-primary-500)',
|
backgroundColor: 'var(--color-primary-500)',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -11,9 +11,40 @@ export interface FileWithUrl extends File {
|
|||||||
|
|
||||||
export interface StorageConfig {
|
export interface StorageConfig {
|
||||||
useIndexedDB: boolean;
|
useIndexedDB: boolean;
|
||||||
// Simplified - no thresholds needed, IndexedDB for everything
|
maxFileSize: number; // Maximum size per file in bytes
|
||||||
|
maxTotalStorage: number; // Maximum total storage in bytes
|
||||||
|
warningThreshold: number; // Warning threshold (percentage 0-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultStorageConfig: StorageConfig = {
|
export const defaultStorageConfig: StorageConfig = {
|
||||||
useIndexedDB: true,
|
useIndexedDB: true,
|
||||||
|
maxFileSize: 100 * 1024 * 1024, // 100MB per file
|
||||||
|
maxTotalStorage: 1024 * 1024 * 1024, // 1GB default, will be updated dynamically
|
||||||
|
warningThreshold: 0.8, // Warn at 80% capacity
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate and update storage limit: half of available storage or 10GB, whichever is smaller
|
||||||
|
export const initializeStorageConfig = async (): Promise<StorageConfig> => {
|
||||||
|
const tenGB = 10 * 1024 * 1024 * 1024; // 10GB in bytes
|
||||||
|
const oneGB = 1024 * 1024 * 1024; // 1GB fallback
|
||||||
|
|
||||||
|
let maxTotalStorage = oneGB; // Default fallback
|
||||||
|
|
||||||
|
// Try to estimate available storage
|
||||||
|
if ('storage' in navigator && 'estimate' in navigator.storage) {
|
||||||
|
try {
|
||||||
|
const estimate = await navigator.storage.estimate();
|
||||||
|
if (estimate.quota) {
|
||||||
|
const halfQuota = estimate.quota / 2;
|
||||||
|
maxTotalStorage = Math.min(halfQuota, tenGB);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Could not estimate storage quota, using 1GB default:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...defaultStorageConfig,
|
||||||
|
maxTotalStorage
|
||||||
|
};
|
||||||
};
|
};
|
Loading…
x
Reference in New Issue
Block a user