mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-02 02:25:21 +00:00
Supported files & single file auto label
This commit is contained in:
parent
bbf92afa36
commit
695b3766ac
@ -12,6 +12,7 @@ import { FileOperation } from '../../types/fileContext';
|
||||
import { fileStorage } from '../../services/fileStorage';
|
||||
import { generateThumbnailForFile } from '../../utils/thumbnailUtils';
|
||||
import { zipFileService } from '../../services/zipFileService';
|
||||
import { detectFileExtension } from '../../utils/fileUtils';
|
||||
import styles from '../pageEditor/PageEditor.module.css';
|
||||
import FileThumbnail from '../pageEditor/FileThumbnail';
|
||||
import DragDropGrid from '../pageEditor/DragDropGrid';
|
||||
@ -34,6 +35,7 @@ interface FileEditorProps {
|
||||
toolMode?: boolean;
|
||||
showUpload?: boolean;
|
||||
showBulkActions?: boolean;
|
||||
supportedExtensions?: string[];
|
||||
}
|
||||
|
||||
const FileEditor = ({
|
||||
@ -41,10 +43,17 @@ const FileEditor = ({
|
||||
onMergeFiles,
|
||||
toolMode = false,
|
||||
showUpload = true,
|
||||
showBulkActions = true
|
||||
showBulkActions = true,
|
||||
supportedExtensions = ["pdf"]
|
||||
}: FileEditorProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Utility function to check if a file extension is supported
|
||||
const isFileSupported = useCallback((fileName: string): boolean => {
|
||||
const extension = detectFileExtension(fileName);
|
||||
return extension ? supportedExtensions.includes(extension) : false;
|
||||
}, [supportedExtensions]);
|
||||
|
||||
// Get file context
|
||||
const fileContext = useFileContext();
|
||||
const {
|
||||
@ -807,6 +816,7 @@ const FileEditor = ({
|
||||
onSplitFile={handleSplitFile}
|
||||
onSetStatus={setStatus}
|
||||
toolMode={toolMode}
|
||||
isSupported={isFileSupported(file.name)}
|
||||
/>
|
||||
)}
|
||||
renderSplitMarker={(file, index) => (
|
||||
|
@ -18,9 +18,10 @@ interface FileCardProps {
|
||||
onEdit?: () => void;
|
||||
isSelected?: boolean;
|
||||
onSelect?: () => void;
|
||||
isSupported?: boolean; // Whether the file format is supported by the current tool
|
||||
}
|
||||
|
||||
const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, onSelect }: FileCardProps) => {
|
||||
const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, onSelect, isSupported = true }: FileCardProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { thumbnail: thumb, isGenerating } = useIndexedDBThumbnail(file);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
@ -35,10 +36,12 @@ const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, o
|
||||
width: 225,
|
||||
minWidth: 180,
|
||||
maxWidth: 260,
|
||||
cursor: onDoubleClick ? "pointer" : undefined,
|
||||
cursor: onDoubleClick && isSupported ? "pointer" : undefined,
|
||||
position: 'relative',
|
||||
border: isSelected ? '2px solid var(--mantine-color-blue-6)' : undefined,
|
||||
backgroundColor: isSelected ? 'var(--mantine-color-blue-0)' : undefined
|
||||
backgroundColor: isSelected ? 'var(--mantine-color-blue-0)' : undefined,
|
||||
opacity: isSupported ? 1 : 0.5,
|
||||
filter: isSupported ? 'none' : 'grayscale(50%)'
|
||||
}}
|
||||
onDoubleClick={onDoubleClick}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
@ -180,6 +183,11 @@ const FileCard = ({ file, onRemove, onDoubleClick, onView, onEdit, isSelected, o
|
||||
DB
|
||||
</Badge>
|
||||
)}
|
||||
{!isSupported && (
|
||||
<Badge color="orange" variant="filled" size="sm">
|
||||
{t("fileManager.unsupported", "Unsupported")}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Button
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Text, Checkbox, Tooltip, ActionIcon, Badge, Modal } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
@ -37,6 +38,7 @@ interface FileThumbnailProps {
|
||||
onViewFile: (fileId: string) => void;
|
||||
onSetStatus: (status: string) => void;
|
||||
toolMode?: boolean;
|
||||
isSupported?: boolean;
|
||||
}
|
||||
|
||||
const FileThumbnail = ({
|
||||
@ -60,7 +62,9 @@ const FileThumbnail = ({
|
||||
onViewFile,
|
||||
onSetStatus,
|
||||
toolMode = false,
|
||||
isSupported = true,
|
||||
}: FileThumbnailProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showHistory, setShowHistory] = useState(false);
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
@ -107,7 +111,9 @@ const FileThumbnail = ({
|
||||
}
|
||||
return 'translateX(0)';
|
||||
})(),
|
||||
transition: isAnimating ? 'none' : 'transform 0.2s ease-in-out'
|
||||
transition: isAnimating ? 'none' : 'transform 0.2s ease-in-out',
|
||||
opacity: isSupported ? 1 : 0.5,
|
||||
filter: isSupported ? 'none' : 'grayscale(50%)'
|
||||
}}
|
||||
draggable
|
||||
onDragStart={() => onDragStart(file.id)}
|
||||
@ -142,9 +148,12 @@ const FileThumbnail = ({
|
||||
checked={selectedFiles.includes(file.id)}
|
||||
onChange={(event) => {
|
||||
event.stopPropagation();
|
||||
onToggleFile(file.id);
|
||||
if (isSupported) {
|
||||
onToggleFile(file.id);
|
||||
}
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
disabled={!isSupported}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
@ -195,6 +204,23 @@ const FileThumbnail = ({
|
||||
{file.pageCount} pages
|
||||
</Badge>
|
||||
|
||||
{/* Unsupported badge */}
|
||||
{!isSupported && (
|
||||
<Badge
|
||||
size="sm"
|
||||
variant="filled"
|
||||
color="orange"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: selectionMode ? 48 : 8, // Avoid overlap with checkbox
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
{t("fileManager.unsupported", "Unsupported")}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{/* File name overlay */}
|
||||
<Text
|
||||
className={styles.pageNumber}
|
||||
@ -240,7 +266,7 @@ const FileThumbnail = ({
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
{!toolMode && (
|
||||
{!toolMode && isSupported && (
|
||||
<>
|
||||
<Tooltip label="View File">
|
||||
<ActionIcon
|
||||
|
@ -20,6 +20,7 @@ interface FileGridProps {
|
||||
onShowAll?: () => void;
|
||||
showingAll?: boolean;
|
||||
onDeleteAll?: () => void;
|
||||
isFileSupported?: (fileName: string) => boolean; // Function to check if file is supported
|
||||
}
|
||||
|
||||
type SortOption = 'date' | 'name' | 'size';
|
||||
@ -37,7 +38,8 @@ const FileGrid = ({
|
||||
maxDisplay,
|
||||
onShowAll,
|
||||
showingAll = false,
|
||||
onDeleteAll
|
||||
onDeleteAll,
|
||||
isFileSupported
|
||||
}: FileGridProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
@ -123,16 +125,18 @@ const FileGrid = ({
|
||||
{displayFiles.map((file, idx) => {
|
||||
const fileId = file.id || file.name;
|
||||
const originalIdx = files.findIndex(f => (f.id || f.name) === fileId);
|
||||
const supported = isFileSupported ? isFileSupported(file.name) : true;
|
||||
return (
|
||||
<FileCard
|
||||
key={fileId + idx}
|
||||
file={file}
|
||||
onRemove={onRemove ? () => onRemove(originalIdx) : undefined}
|
||||
onDoubleClick={onDoubleClick ? () => onDoubleClick(file) : undefined}
|
||||
onView={onView ? () => onView(file) : undefined}
|
||||
onEdit={onEdit ? () => onEdit(file) : undefined}
|
||||
onDoubleClick={onDoubleClick && supported ? () => onDoubleClick(file) : undefined}
|
||||
onView={onView && supported ? () => onView(file) : undefined}
|
||||
onEdit={onEdit && supported ? () => onEdit(file) : undefined}
|
||||
isSelected={selectedFiles.includes(fileId)}
|
||||
onSelect={onSelect ? () => onSelect(fileId) : undefined}
|
||||
onSelect={onSelect && supported ? () => onSelect(fileId) : undefined}
|
||||
isSupported={supported}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -5,6 +5,7 @@ import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { fileStorage } from '../../services/fileStorage';
|
||||
import { FileWithUrl } from '../../types/file';
|
||||
import { detectFileExtension } from '../../utils/fileUtils';
|
||||
import FileGrid from './FileGrid';
|
||||
import MultiSelectControls from './MultiSelectControls';
|
||||
import { useFileManager } from '../../hooks/useFileManager';
|
||||
@ -20,6 +21,7 @@ interface FileUploadSelectorProps {
|
||||
onFileSelect?: (file: File) => void;
|
||||
onFilesSelect: (files: File[]) => void;
|
||||
accept?: string[];
|
||||
supportedExtensions?: string[]; // Extensions this tool supports (e.g., ['pdf', 'jpg', 'png'])
|
||||
|
||||
// Loading state
|
||||
loading?: boolean;
|
||||
@ -38,6 +40,7 @@ const FileUploadSelector = ({
|
||||
onFileSelect,
|
||||
onFilesSelect,
|
||||
accept = ["application/pdf", "application/zip", "application/x-zip-compressed"],
|
||||
supportedExtensions = ["pdf"], // Default to PDF only for most tools
|
||||
loading = false,
|
||||
disabled = false,
|
||||
showRecentFiles = true,
|
||||
@ -51,6 +54,12 @@ const FileUploadSelector = ({
|
||||
|
||||
const { loadRecentFiles, handleRemoveFile, storeFile, convertToFile, createFileSelectionHandlers } = useFileManager();
|
||||
|
||||
// Utility function to check if a file extension is supported
|
||||
const isFileSupported = useCallback((fileName: string): boolean => {
|
||||
const extension = detectFileExtension(fileName);
|
||||
return extension ? supportedExtensions.includes(extension) : false;
|
||||
}, [supportedExtensions]);
|
||||
|
||||
const refreshRecentFiles = useCallback(async () => {
|
||||
const files = await loadRecentFiles();
|
||||
setRecentFiles(files);
|
||||
@ -227,6 +236,7 @@ const FileUploadSelector = ({
|
||||
selectedFiles={selectedFiles}
|
||||
showSearch={true}
|
||||
showSort={true}
|
||||
isFileSupported={isFileSupported}
|
||||
onDeleteAll={async () => {
|
||||
await Promise.all(recentFiles.map(async (file) => {
|
||||
await fileStorage.deleteFile(file.id || file.name);
|
||||
|
@ -64,20 +64,35 @@ const ConvertSettings = ({
|
||||
|
||||
// Enhanced FROM options with endpoint availability
|
||||
const enhancedFromOptions = useMemo(() => {
|
||||
return FROM_FORMAT_OPTIONS.map(option => {
|
||||
const baseOptions = FROM_FORMAT_OPTIONS.map(option => {
|
||||
// Check if this source format has any available conversions
|
||||
const availableConversions = getAvailableToExtensions(option.value) || [];
|
||||
const hasAvailableConversions = availableConversions.some(targetOption =>
|
||||
isConversionAvailable(option.value, targetOption.value)
|
||||
);
|
||||
|
||||
|
||||
return {
|
||||
...option,
|
||||
enabled: hasAvailableConversions
|
||||
};
|
||||
});
|
||||
}, [getAvailableToExtensions, endpointStatus]);
|
||||
|
||||
// Add dynamic format option if current selection is a file-<extension> format
|
||||
if (parameters.fromExtension && parameters.fromExtension.startsWith('file-')) {
|
||||
const extension = parameters.fromExtension.replace('file-', '');
|
||||
const dynamicOption = {
|
||||
value: parameters.fromExtension,
|
||||
label: extension.toUpperCase(),
|
||||
group: 'File',
|
||||
enabled: true
|
||||
};
|
||||
|
||||
// Add the dynamic option at the beginning
|
||||
return [dynamicOption, ...baseOptions];
|
||||
}
|
||||
|
||||
return baseOptions;
|
||||
}, [getAvailableToExtensions, endpointStatus, parameters.fromExtension]);
|
||||
|
||||
// Enhanced TO options with endpoint availability
|
||||
const enhancedToOptions = useMemo(() => {
|
||||
|
@ -93,15 +93,18 @@ export const useConvertParameters = (): ConvertParametersHook => {
|
||||
|
||||
if (!fromExtension || !toExtension) return false;
|
||||
|
||||
// Check if conversion is supported
|
||||
const supportedToExtensions = CONVERSION_MATRIX[fromExtension];
|
||||
if (!supportedToExtensions || !supportedToExtensions.includes(toExtension)) {
|
||||
return false;
|
||||
// Handle dynamic format identifiers (file-<extension>)
|
||||
let supportedToExtensions: string[] = [];
|
||||
if (fromExtension.startsWith('file-')) {
|
||||
// Dynamic format - use 'any' conversion options
|
||||
supportedToExtensions = CONVERSION_MATRIX['any'] || [];
|
||||
} else {
|
||||
// Regular format - check conversion matrix
|
||||
supportedToExtensions = CONVERSION_MATRIX[fromExtension] || [];
|
||||
}
|
||||
|
||||
// Additional validation for image conversions
|
||||
if (['png', 'jpg'].includes(toExtension)) {
|
||||
return parameters.imageOptions.dpi >= 72 && parameters.imageOptions.dpi <= 600;
|
||||
if (!supportedToExtensions.includes(toExtension)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -123,6 +126,12 @@ export const useConvertParameters = (): ConvertParametersHook => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dynamic format identifiers (file-<extension>)
|
||||
if (fromExtension.startsWith('file-')) {
|
||||
// Dynamic format - use file-to-pdf endpoint
|
||||
return 'file-to-pdf';
|
||||
}
|
||||
|
||||
return getEndpointNameUtil(fromExtension, toExtension);
|
||||
};
|
||||
|
||||
@ -142,12 +151,27 @@ export const useConvertParameters = (): ConvertParametersHook => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dynamic format identifiers (file-<extension>)
|
||||
if (fromExtension.startsWith('file-')) {
|
||||
// Dynamic format - use file-to-pdf endpoint
|
||||
return '/api/v1/convert/file/pdf';
|
||||
}
|
||||
|
||||
return getEndpointUrl(fromExtension, toExtension);
|
||||
};
|
||||
|
||||
const getAvailableToExtensions = (fromExtension: string) => {
|
||||
if (!fromExtension) return [];
|
||||
|
||||
// Handle dynamic format identifiers (file-<extension>)
|
||||
if (fromExtension.startsWith('file-')) {
|
||||
// Dynamic format - use 'any' conversion options (file-to-pdf)
|
||||
const supportedExtensions = CONVERSION_MATRIX['any'] || [];
|
||||
return TO_FORMAT_OPTIONS.filter(option =>
|
||||
supportedExtensions.includes(option.value)
|
||||
);
|
||||
}
|
||||
|
||||
let supportedExtensions = CONVERSION_MATRIX[fromExtension] || [];
|
||||
|
||||
// If no explicit conversion exists, but file-to-pdf might be available,
|
||||
@ -180,9 +204,13 @@ export const useConvertParameters = (): ConvertParametersHook => {
|
||||
let fromExt = detectedExt;
|
||||
let availableTargets = detectedExt ? CONVERSION_MATRIX[detectedExt] || [] : [];
|
||||
|
||||
// If no explicit conversion exists for this file type, fall back to 'any'
|
||||
// which will attempt file-to-pdf conversion if available
|
||||
if (availableTargets.length === 0) {
|
||||
// If no explicit conversion exists for this file type, create a dynamic format entry
|
||||
// and fall back to 'any' conversion logic for the actual endpoint
|
||||
if (availableTargets.length === 0 && detectedExt) {
|
||||
fromExt = `file-${detectedExt}`; // Create dynamic format identifier
|
||||
availableTargets = CONVERSION_MATRIX['any'] || [];
|
||||
} else if (availableTargets.length === 0) {
|
||||
// No extension detected - fall back to 'any'
|
||||
fromExt = 'any';
|
||||
availableTargets = CONVERSION_MATRIX['any'] || [];
|
||||
}
|
||||
|
@ -35,7 +35,25 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
||||
maxFiles: -1,
|
||||
category: "manipulation",
|
||||
description: "Change to and from PDF and other formats",
|
||||
endpoints: ["pdf-to-img", "img-to-pdf", "pdf-to-word", "pdf-to-presentation", "pdf-to-text", "pdf-to-html", "pdf-to-xml", "html-to-pdf", "markdown-to-pdf", "file-to-pdf"]
|
||||
endpoints: ["pdf-to-img", "img-to-pdf", "pdf-to-word", "pdf-to-presentation", "pdf-to-text", "pdf-to-html", "pdf-to-xml", "html-to-pdf", "markdown-to-pdf", "file-to-pdf"],
|
||||
supportedFormats: [
|
||||
// Microsoft Office
|
||||
"doc", "docx", "dot", "dotx", "csv", "xls", "xlsx", "xlt", "xltx", "slk", "dif", "ppt", "pptx",
|
||||
// OpenDocument
|
||||
"odt", "ott", "ods", "ots", "odp", "otp", "odg", "otg",
|
||||
// Text formats
|
||||
"txt", "text", "xml", "rtf", "html", "lwp", "md",
|
||||
// Images
|
||||
"bmp", "gif", "jpeg", "jpg", "png", "tif", "tiff", "pbm", "pgm", "ppm", "ras", "xbm", "xpm", "svg", "svm", "wmf", "webp",
|
||||
// StarOffice
|
||||
"sda", "sdc", "sdd", "sdw", "stc", "std", "sti", "stw", "sxd", "sxg", "sxi", "sxw",
|
||||
// Email formats
|
||||
"eml",
|
||||
// Archive formats
|
||||
"zip",
|
||||
// Other
|
||||
"dbf", "fods", "vsd", "vor", "vor3", "vor4", "uop", "pct", "ps", "pdf"
|
||||
]
|
||||
},
|
||||
swagger: {
|
||||
id: "swagger",
|
||||
|
@ -197,6 +197,7 @@ function HomePageContent() {
|
||||
files.forEach(addToActiveFiles);
|
||||
}}
|
||||
accept={["*/*"]}
|
||||
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
|
||||
loading={false}
|
||||
showRecentFiles={true}
|
||||
maxRecentFiles={8}
|
||||
@ -207,6 +208,7 @@ function HomePageContent() {
|
||||
toolMode={!!selectedToolKey}
|
||||
showUpload={true}
|
||||
showBulkActions={!selectedToolKey}
|
||||
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
|
||||
{...(!selectedToolKey && {
|
||||
onOpenPageEditor: (file) => {
|
||||
handleViewChange("pageEditor");
|
||||
@ -287,6 +289,7 @@ function HomePageContent() {
|
||||
files.forEach(addToActiveFiles);
|
||||
}}
|
||||
accept={["*/*"]}
|
||||
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
|
||||
loading={false}
|
||||
showRecentFiles={true}
|
||||
maxRecentFiles={8}
|
||||
|
Loading…
x
Reference in New Issue
Block a user