diff --git a/app/core/src/main/resources/messages_en_GB.properties b/app/core/src/main/resources/messages_en_GB.properties index d63b1c6c4..405643877 100644 --- a/app/core/src/main/resources/messages_en_GB.properties +++ b/app/core/src/main/resources/messages_en_GB.properties @@ -1916,6 +1916,7 @@ fileManager.storageError=Storage error occurred fileManager.storageLow=Storage is running low. Consider removing old files. fileManager.uploadError=Failed to upload some files. fileManager.supportMessage=Powered by browser database storage for unlimited capacity +fileManager.loadingFiles=Loading files... # Page Editor pageEditor.title=Page Editor diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 826c48960..b1646e76f 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -3153,6 +3153,9 @@ "addFiles": "Add Files", "dragFilesInOrClick": "Drag files in or click \"Add Files\" to browse" }, + "fileEditor": { + "addFiles": "Add Files" + }, "fileManager": { "title": "Upload PDF Files", "subtitle": "Add files to your storage for easy access across tools", @@ -3188,7 +3191,6 @@ "googleDriveShort": "Drive", "myFiles": "My Files", "noRecentFiles": "No recent files found", - "dropFilesHint": "Drop files here to upload", "googleDriveNotAvailable": "Google Drive integration not available", "openFiles": "Open Files", "openFile": "Open File", diff --git a/frontend/src/components/FileManager.tsx b/frontend/src/components/FileManager.tsx index e75b95e28..fb26a3a3a 100644 --- a/frontend/src/components/FileManager.tsx +++ b/frontend/src/components/FileManager.tsx @@ -20,7 +20,7 @@ const FileManager: React.FC = ({ selectedTool }) => { const [isDragging, setIsDragging] = useState(false); const [isMobile, setIsMobile] = useState(false); - const { loadRecentFiles, handleRemoveFile } = useFileManager(); + const { loadRecentFiles, handleRemoveFile, loading } = useFileManager(); // File management handlers const isFileSupported = useCallback((fileName: string) => { @@ -123,7 +123,6 @@ const FileManager: React.FC = ({ selectedTool }) => { onDrop={handleNewFileUpload} onDragEnter={() => setIsDragging(true)} onDragLeave={() => setIsDragging(false)} - accept={{}} multiple={true} activateOnClick={false} style={{ @@ -147,6 +146,7 @@ const FileManager: React.FC = ({ selectedTool }) => { onFileRemove={handleRemoveFileByIndex} modalHeight={modalHeight} refreshRecentFiles={refreshRecentFiles} + isLoading={loading} > {isMobile ? : } diff --git a/frontend/src/components/fileEditor/AddFileCard.tsx b/frontend/src/components/fileEditor/AddFileCard.tsx new file mode 100644 index 000000000..dbc319c68 --- /dev/null +++ b/frontend/src/components/fileEditor/AddFileCard.tsx @@ -0,0 +1,177 @@ +import React, { useRef, useState } from 'react'; +import { Button, Group, useMantineColorScheme } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import AddIcon from '@mui/icons-material/Add'; +import { useFilesModalContext } from '../../contexts/FilesModalContext'; +import LocalIcon from '../shared/LocalIcon'; +import { BASE_PATH } from '../../constants/app'; +import styles from './FileEditor.module.css'; + +interface AddFileCardProps { + onFileSelect: (files: File[]) => void; + accept?: string; + multiple?: boolean; +} + +const AddFileCard = ({ + onFileSelect, + accept = "*/*", + multiple = true +}: AddFileCardProps) => { + const { t } = useTranslation(); + const fileInputRef = useRef(null); + const { openFilesModal } = useFilesModalContext(); + const { colorScheme } = useMantineColorScheme(); + const [isUploadHover, setIsUploadHover] = useState(false); + + const handleCardClick = () => { + openFilesModal(); + }; + + const handleNativeUploadClick = (e: React.MouseEvent) => { + e.stopPropagation(); + fileInputRef.current?.click(); + }; + + const handleOpenFilesModal = (e: React.MouseEvent) => { + e.stopPropagation(); + openFilesModal(); + }; + + const handleFileChange = (event: React.ChangeEvent) => { + const files = Array.from(event.target.files || []); + if (files.length > 0) { + onFileSelect(files); + } + // Reset input so same files can be selected again + event.target.value = ''; + }; + + return ( + <> + + +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleCardClick(); + } + }} + > + {/* Header bar - matches FileEditorThumbnail structure */} +
+
+ +
+
+ {t('fileEditor.addFiles', 'Add Files')} +
+
+
+ + {/* Main content area */} +
+ {/* Stirling PDF Branding */} + + Stirling PDF + + + {/* Add Files + Native Upload Buttons - styled like LandingPage */} +
setIsUploadHover(false)} + > + + +
+ + {/* Instruction Text */} + + {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + +
+
+ + ); +}; + +export default AddFileCard; \ No newline at end of file diff --git a/frontend/src/components/fileEditor/FileEditor.module.css b/frontend/src/components/fileEditor/FileEditor.module.css index ccabc2fa7..173738c29 100644 --- a/frontend/src/components/fileEditor/FileEditor.module.css +++ b/frontend/src/components/fileEditor/FileEditor.module.css @@ -304,4 +304,84 @@ /* Light mode selected header stroke override */ :global([data-mantine-color-scheme="light"]) .card[data-selected="true"] { outline-color: #3B4B6E; +} + +/* ========================= + Add File Card Styles + ========================= */ + +.addFileCard { + background: var(--file-card-bg); + border: 2px dashed var(--border-default); + border-radius: 0.0625rem; + cursor: pointer; + transition: all 0.18s ease; + max-width: 100%; + max-height: 100%; + overflow: hidden; + margin-left: 0.5rem; + margin-right: 0.5rem; + opacity: 0.7; +} + +.addFileCard:hover { + opacity: 1; + border-color: var(--color-blue-500); + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.addFileCard:focus { + outline: 2px solid var(--color-blue-500); + outline-offset: 2px; +} + +.addFileHeader { + background: var(--bg-subtle); + color: var(--text-secondary); + border-bottom: 1px solid var(--border-default); +} + +.addFileCard:hover .addFileHeader { + background: var(--color-blue-500); + color: white; +} + +.addFileContent { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 1.5rem 1rem; + gap: 0.5rem; +} + +.addFileIcon { + display: flex; + align-items: center; + justify-content: center; + width: 5rem; + height: 5rem; + border-radius: 50%; + background: var(--bg-subtle); + transition: background-color 0.18s ease; +} + +.addFileCard:hover .addFileIcon { + background: var(--color-blue-50); +} + +.addFileText { + font-weight: 500; + transition: color 0.18s ease; +} + +.addFileCard:hover .addFileText { + color: var(--text-primary); +} + +.addFileSubtext { + font-size: 0.875rem; + opacity: 0.8; } \ No newline at end of file diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index 8dbd83480..00bf22480 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -8,6 +8,7 @@ import { useNavigationActions } from '../../contexts/NavigationContext'; import { zipFileService } from '../../services/zipFileService'; import { detectFileExtension } from '../../utils/fileUtils'; import FileEditorThumbnail from './FileEditorThumbnail'; +import AddFileCard from './AddFileCard'; import FilePickerModal from '../shared/FilePickerModal'; import SkeletonLoader from '../shared/SkeletonLoader'; import { FileId, StirlingFile } from '../../types/fileContext'; @@ -171,8 +172,8 @@ const FileEditor = ({ // Process all extracted files if (allExtractedFiles.length > 0) { - // Add files to context (they will be processed automatically) - await addFiles(allExtractedFiles); + // Add files to context and select them automatically + await addFiles(allExtractedFiles, { selectFiles: true }); showStatus(`Added ${allExtractedFiles.length} files`, 'success'); } } catch (err) { @@ -405,6 +406,14 @@ const FileEditor = ({ pointerEvents: 'auto' }} > + {/* Add File Card - only show when files exist */} + {activeStirlingFileStubs.length > 0 && ( + + )} + {activeStirlingFileStubs.map((record, index) => { return ( { + const { t } = useTranslation(); + const { colorScheme } = useMantineColorScheme(); + const { onLocalFileClick } = useFileManagerContext(); + const [isUploadHover, setIsUploadHover] = useState(false); + + const handleUploadClick = () => { + onLocalFileClick(); + }; + + return ( +
+ {/* Container */} +
+ {/* No Recent Files Message */} + + + + {t('fileManager.noRecentFiles', 'No recent files')} + + + + {/* Stirling PDF Logo */} + + Stirling PDF + + + {/* Upload Button */} +
setIsUploadHover(false)} + > + +
+ + {/* Instruction Text */} + + {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + +
+
+ ); +}; + +export default EmptyFilesState; diff --git a/frontend/src/components/fileManager/FileListArea.tsx b/frontend/src/components/fileManager/FileListArea.tsx index 842d0bf0e..556ecc4f1 100644 --- a/frontend/src/components/fileManager/FileListArea.tsx +++ b/frontend/src/components/fileManager/FileListArea.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { Center, ScrollArea, Text, Stack } from '@mantine/core'; import CloudIcon from '@mui/icons-material/Cloud'; -import HistoryIcon from '@mui/icons-material/History'; import { useTranslation } from 'react-i18next'; import FileListItem from './FileListItem'; import FileHistoryGroup from './FileHistoryGroup'; +import EmptyFilesState from './EmptyFilesState'; import { useFileManagerContext } from '../../contexts/FileManagerContext'; interface FileListAreaProps { @@ -29,6 +29,7 @@ const FileListArea: React.FC = ({ onFileDoubleClick, onDownloadSingle, isFileSupported, + isLoading, } = useFileManagerContext(); const { t } = useTranslation(); @@ -43,15 +44,11 @@ const FileListArea: React.FC = ({ scrollbarSize={8} > - {recentFiles.length === 0 ? ( + {recentFiles.length === 0 && !isLoading ? ( + + ) : recentFiles.length === 0 && isLoading ? (
- - - {t('fileManager.noRecentFiles', 'No recent files')} - - {t('fileManager.dropFilesHint', 'Drop files anywhere to upload')} - - + {t('fileManager.loadingFiles', 'Loading files...')}
) : ( filteredFiles.map((file, index) => { diff --git a/frontend/src/components/fileManager/HiddenFileInput.tsx b/frontend/src/components/fileManager/HiddenFileInput.tsx index 05f35aae1..8dee9e278 100644 --- a/frontend/src/components/fileManager/HiddenFileInput.tsx +++ b/frontend/src/components/fileManager/HiddenFileInput.tsx @@ -9,7 +9,6 @@ const HiddenFileInput: React.FC = () => { ref={fileInputRef} type="file" multiple={true} - accept={["*/*"] as any} onChange={onFileInputChange} style={{ display: 'none' }} data-testid="file-input" diff --git a/frontend/src/components/shared/LandingPage.tsx b/frontend/src/components/shared/LandingPage.tsx index 787b0e565..5f1fe8d8e 100644 --- a/frontend/src/components/shared/LandingPage.tsx +++ b/frontend/src/components/shared/LandingPage.tsx @@ -41,7 +41,7 @@ const LandingPage = () => { {/* White PDF Page Background */} { ref={fileInputRef} type="file" multiple - accept=".pdf,.zip" + accept="*/*" onChange={handleFileSelect} style={{ display: 'none' }} /> diff --git a/frontend/src/contexts/FileManagerContext.tsx b/frontend/src/contexts/FileManagerContext.tsx index 6f3afe21b..fb82e9071 100644 --- a/frontend/src/contexts/FileManagerContext.tsx +++ b/frontend/src/contexts/FileManagerContext.tsx @@ -18,6 +18,7 @@ interface FileManagerContextValue { expandedFileIds: Set; fileGroups: Map; loadedHistoryFiles: Map; + isLoading: boolean; // Handlers onSourceChange: (source: 'recent' | 'local' | 'drive') => void; @@ -58,6 +59,7 @@ interface FileManagerProviderProps { onFileRemove: (index: number) => void; modalHeight: string; refreshRecentFiles: () => Promise; + isLoading: boolean; } export const FileManagerProvider: React.FC = ({ @@ -71,6 +73,7 @@ export const FileManagerProvider: React.FC = ({ onFileRemove, modalHeight, refreshRecentFiles, + isLoading, }) => { const [activeSource, setActiveSource] = useState<'recent' | 'local' | 'drive'>('recent'); const [selectedFileIds, setSelectedFileIds] = useState([]); @@ -574,6 +577,7 @@ export const FileManagerProvider: React.FC = ({ expandedFileIds, fileGroups, loadedHistoryFiles, + isLoading, // Handlers onSourceChange: handleSourceChange, @@ -607,6 +611,7 @@ export const FileManagerProvider: React.FC = ({ expandedFileIds, fileGroups, loadedHistoryFiles, + isLoading, handleSourceChange, handleLocalFileClick, handleFileSelect,