mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
Feature/v2/more landing zones (#4575)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered 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. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
989eea9e24
commit
25154e4dbe
@ -1916,6 +1916,7 @@ fileManager.storageError=Storage error occurred
|
|||||||
fileManager.storageLow=Storage is running low. Consider removing old files.
|
fileManager.storageLow=Storage is running low. Consider removing old files.
|
||||||
fileManager.uploadError=Failed to upload some files.
|
fileManager.uploadError=Failed to upload some files.
|
||||||
fileManager.supportMessage=Powered by browser database storage for unlimited capacity
|
fileManager.supportMessage=Powered by browser database storage for unlimited capacity
|
||||||
|
fileManager.loadingFiles=Loading files...
|
||||||
|
|
||||||
# Page Editor
|
# Page Editor
|
||||||
pageEditor.title=Page Editor
|
pageEditor.title=Page Editor
|
||||||
|
|||||||
@ -3153,6 +3153,9 @@
|
|||||||
"addFiles": "Add Files",
|
"addFiles": "Add Files",
|
||||||
"dragFilesInOrClick": "Drag files in or click \"Add Files\" to browse"
|
"dragFilesInOrClick": "Drag files in or click \"Add Files\" to browse"
|
||||||
},
|
},
|
||||||
|
"fileEditor": {
|
||||||
|
"addFiles": "Add Files"
|
||||||
|
},
|
||||||
"fileManager": {
|
"fileManager": {
|
||||||
"title": "Upload PDF Files",
|
"title": "Upload PDF Files",
|
||||||
"subtitle": "Add files to your storage for easy access across tools",
|
"subtitle": "Add files to your storage for easy access across tools",
|
||||||
@ -3188,7 +3191,6 @@
|
|||||||
"googleDriveShort": "Drive",
|
"googleDriveShort": "Drive",
|
||||||
"myFiles": "My Files",
|
"myFiles": "My Files",
|
||||||
"noRecentFiles": "No recent files found",
|
"noRecentFiles": "No recent files found",
|
||||||
"dropFilesHint": "Drop files here to upload",
|
|
||||||
"googleDriveNotAvailable": "Google Drive integration not available",
|
"googleDriveNotAvailable": "Google Drive integration not available",
|
||||||
"openFiles": "Open Files",
|
"openFiles": "Open Files",
|
||||||
"openFile": "Open File",
|
"openFile": "Open File",
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
|||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
|
||||||
const { loadRecentFiles, handleRemoveFile } = useFileManager();
|
const { loadRecentFiles, handleRemoveFile, loading } = useFileManager();
|
||||||
|
|
||||||
// File management handlers
|
// File management handlers
|
||||||
const isFileSupported = useCallback((fileName: string) => {
|
const isFileSupported = useCallback((fileName: string) => {
|
||||||
@ -123,7 +123,6 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
|||||||
onDrop={handleNewFileUpload}
|
onDrop={handleNewFileUpload}
|
||||||
onDragEnter={() => setIsDragging(true)}
|
onDragEnter={() => setIsDragging(true)}
|
||||||
onDragLeave={() => setIsDragging(false)}
|
onDragLeave={() => setIsDragging(false)}
|
||||||
accept={{}}
|
|
||||||
multiple={true}
|
multiple={true}
|
||||||
activateOnClick={false}
|
activateOnClick={false}
|
||||||
style={{
|
style={{
|
||||||
@ -147,6 +146,7 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
|||||||
onFileRemove={handleRemoveFileByIndex}
|
onFileRemove={handleRemoveFileByIndex}
|
||||||
modalHeight={modalHeight}
|
modalHeight={modalHeight}
|
||||||
refreshRecentFiles={refreshRecentFiles}
|
refreshRecentFiles={refreshRecentFiles}
|
||||||
|
isLoading={loading}
|
||||||
>
|
>
|
||||||
{isMobile ? <MobileLayout /> : <DesktopLayout />}
|
{isMobile ? <MobileLayout /> : <DesktopLayout />}
|
||||||
</FileManagerProvider>
|
</FileManagerProvider>
|
||||||
|
|||||||
177
frontend/src/components/fileEditor/AddFileCard.tsx
Normal file
177
frontend/src/components/fileEditor/AddFileCard.tsx
Normal file
@ -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<HTMLInputElement>(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<HTMLInputElement>) => {
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept={accept}
|
||||||
|
multiple={multiple}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`${styles.addFileCard} w-[18rem] h-[22rem] select-none flex flex-col shadow-sm transition-all relative cursor-pointer`}
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
aria-label={t('fileEditor.addFiles', 'Add files')}
|
||||||
|
onClick={handleCardClick}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleCardClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header bar - matches FileEditorThumbnail structure */}
|
||||||
|
<div className={`${styles.header} ${styles.addFileHeader}`}>
|
||||||
|
<div className={styles.logoMark}>
|
||||||
|
<AddIcon sx={{ color: 'inherit', fontSize: '1.5rem' }} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.headerIndex}>
|
||||||
|
{t('fileEditor.addFiles', 'Add Files')}
|
||||||
|
</div>
|
||||||
|
<div className={styles.kebab} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main content area */}
|
||||||
|
<div className={styles.addFileContent}>
|
||||||
|
{/* Stirling PDF Branding */}
|
||||||
|
<Group gap="xs" align="center">
|
||||||
|
<img
|
||||||
|
src={colorScheme === 'dark' ? `${BASE_PATH}/branding/StirlingPDFLogoWhiteText.svg` : `${BASE_PATH}/branding/StirlingPDFLogoGreyText.svg`}
|
||||||
|
alt="Stirling PDF"
|
||||||
|
style={{ height: '2.2rem', width: 'auto' }}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Add Files + Native Upload Buttons - styled like LandingPage */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '0.6rem',
|
||||||
|
width: '100%',
|
||||||
|
marginTop: '0.8rem',
|
||||||
|
marginBottom: '0.8rem'
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => setIsUploadHover(false)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--landing-button-bg)',
|
||||||
|
color: 'var(--landing-button-color)',
|
||||||
|
border: '1px solid var(--landing-button-border)',
|
||||||
|
borderRadius: '2rem',
|
||||||
|
height: '38px',
|
||||||
|
paddingLeft: isUploadHover ? 0 : '1rem',
|
||||||
|
paddingRight: isUploadHover ? 0 : '1rem',
|
||||||
|
width: isUploadHover ? '58px' : 'calc(100% - 58px - 0.6rem)',
|
||||||
|
minWidth: isUploadHover ? '58px' : undefined,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
transition: 'width .5s ease, padding .5s ease'
|
||||||
|
}}
|
||||||
|
onClick={handleOpenFilesModal}
|
||||||
|
onMouseEnter={() => setIsUploadHover(false)}
|
||||||
|
>
|
||||||
|
<LocalIcon icon="add" width="1.5rem" height="1.5rem" className="text-[var(--accent-interactive)]" />
|
||||||
|
{!isUploadHover && (
|
||||||
|
<span>
|
||||||
|
{t('landing.addFiles', 'Add Files')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
aria-label="Upload"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--landing-button-bg)',
|
||||||
|
color: 'var(--landing-button-color)',
|
||||||
|
border: '1px solid var(--landing-button-border)',
|
||||||
|
borderRadius: '1rem',
|
||||||
|
height: '38px',
|
||||||
|
width: isUploadHover ? 'calc(100% - 58px - 0.6rem)' : '58px',
|
||||||
|
minWidth: '58px',
|
||||||
|
paddingLeft: isUploadHover ? '1rem' : 0,
|
||||||
|
paddingRight: isUploadHover ? '1rem' : 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
transition: 'width .5s ease, padding .5s ease'
|
||||||
|
}}
|
||||||
|
onClick={handleNativeUploadClick}
|
||||||
|
onMouseEnter={() => setIsUploadHover(true)}
|
||||||
|
>
|
||||||
|
<LocalIcon icon="upload" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
|
||||||
|
{isUploadHover && (
|
||||||
|
<span style={{ marginLeft: '.5rem' }}>
|
||||||
|
{t('landing.uploadFromComputer', 'Upload from computer')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Instruction Text */}
|
||||||
|
<span
|
||||||
|
className="text-[var(--accent-interactive)]"
|
||||||
|
style={{ fontSize: '.8rem', textAlign: 'center', marginTop: '0.5rem' }}
|
||||||
|
>
|
||||||
|
{t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddFileCard;
|
||||||
@ -305,3 +305,83 @@
|
|||||||
:global([data-mantine-color-scheme="light"]) .card[data-selected="true"] {
|
:global([data-mantine-color-scheme="light"]) .card[data-selected="true"] {
|
||||||
outline-color: #3B4B6E;
|
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;
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import { useNavigationActions } from '../../contexts/NavigationContext';
|
|||||||
import { zipFileService } from '../../services/zipFileService';
|
import { zipFileService } from '../../services/zipFileService';
|
||||||
import { detectFileExtension } from '../../utils/fileUtils';
|
import { detectFileExtension } from '../../utils/fileUtils';
|
||||||
import FileEditorThumbnail from './FileEditorThumbnail';
|
import FileEditorThumbnail from './FileEditorThumbnail';
|
||||||
|
import AddFileCard from './AddFileCard';
|
||||||
import FilePickerModal from '../shared/FilePickerModal';
|
import FilePickerModal from '../shared/FilePickerModal';
|
||||||
import SkeletonLoader from '../shared/SkeletonLoader';
|
import SkeletonLoader from '../shared/SkeletonLoader';
|
||||||
import { FileId, StirlingFile } from '../../types/fileContext';
|
import { FileId, StirlingFile } from '../../types/fileContext';
|
||||||
@ -171,8 +172,8 @@ const FileEditor = ({
|
|||||||
|
|
||||||
// Process all extracted files
|
// Process all extracted files
|
||||||
if (allExtractedFiles.length > 0) {
|
if (allExtractedFiles.length > 0) {
|
||||||
// Add files to context (they will be processed automatically)
|
// Add files to context and select them automatically
|
||||||
await addFiles(allExtractedFiles);
|
await addFiles(allExtractedFiles, { selectFiles: true });
|
||||||
showStatus(`Added ${allExtractedFiles.length} files`, 'success');
|
showStatus(`Added ${allExtractedFiles.length} files`, 'success');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -405,6 +406,14 @@ const FileEditor = ({
|
|||||||
pointerEvents: 'auto'
|
pointerEvents: 'auto'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Add File Card - only show when files exist */}
|
||||||
|
{activeStirlingFileStubs.length > 0 && (
|
||||||
|
<AddFileCard
|
||||||
|
key="add-file-card"
|
||||||
|
onFileSelect={handleFileUpload}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{activeStirlingFileStubs.map((record, index) => {
|
{activeStirlingFileStubs.map((record, index) => {
|
||||||
return (
|
return (
|
||||||
<FileEditorThumbnail
|
<FileEditorThumbnail
|
||||||
|
|||||||
115
frontend/src/components/fileManager/EmptyFilesState.tsx
Normal file
115
frontend/src/components/fileManager/EmptyFilesState.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button, Group, Text, Stack, useMantineColorScheme } from '@mantine/core';
|
||||||
|
import HistoryIcon from '@mui/icons-material/History';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
||||||
|
import LocalIcon from '../shared/LocalIcon';
|
||||||
|
import { BASE_PATH } from '../../constants/app';
|
||||||
|
|
||||||
|
const EmptyFilesState: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
const { onLocalFileClick } = useFileManagerContext();
|
||||||
|
const [isUploadHover, setIsUploadHover] = useState(false);
|
||||||
|
|
||||||
|
const handleUploadClick = () => {
|
||||||
|
onLocalFileClick();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '2rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Container */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
padding: '3rem 2rem',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '1.5rem',
|
||||||
|
minWidth: '20rem',
|
||||||
|
maxWidth: '28rem',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* No Recent Files Message */}
|
||||||
|
<Stack align="center" gap="sm">
|
||||||
|
<HistoryIcon style={{ fontSize: '3rem', color: 'var(--mantine-color-gray-5)' }} />
|
||||||
|
<Text c="dimmed" ta="center" size="lg">
|
||||||
|
{t('fileManager.noRecentFiles', 'No recent files')}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Stirling PDF Logo */}
|
||||||
|
<Group gap="xs" align="center">
|
||||||
|
<img
|
||||||
|
src={colorScheme === 'dark' ? `${BASE_PATH}/branding/StirlingPDFLogoWhiteText.svg` : `${BASE_PATH}/branding/StirlingPDFLogoGreyText.svg`}
|
||||||
|
alt="Stirling PDF"
|
||||||
|
style={{ height: '2.2rem', width: 'auto' }}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Upload Button */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '100%',
|
||||||
|
marginTop: '0.5rem',
|
||||||
|
marginBottom: '0.5rem'
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => setIsUploadHover(false)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
aria-label="Upload"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--bg-file-manager)',
|
||||||
|
color: 'var(--landing-button-color)',
|
||||||
|
border: '1px solid var(--landing-button-border)',
|
||||||
|
borderRadius: isUploadHover ? '2rem' : '1rem',
|
||||||
|
height: '38px',
|
||||||
|
width: isUploadHover ? '100%' : '58px',
|
||||||
|
minWidth: '58px',
|
||||||
|
paddingLeft: isUploadHover ? '1rem' : 0,
|
||||||
|
paddingRight: isUploadHover ? '1rem' : 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
transition: 'width .5s ease, padding .5s ease, border-radius .5s ease'
|
||||||
|
}}
|
||||||
|
onClick={handleUploadClick}
|
||||||
|
onMouseEnter={() => setIsUploadHover(true)}
|
||||||
|
>
|
||||||
|
<LocalIcon icon="upload" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
|
||||||
|
{isUploadHover && (
|
||||||
|
<span style={{ marginLeft: '.5rem' }}>
|
||||||
|
{t('landing.uploadFromComputer', 'Upload from computer')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Instruction Text */}
|
||||||
|
<span
|
||||||
|
className="text-[var(--accent-interactive)]"
|
||||||
|
style={{ fontSize: '.8rem', textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
{t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmptyFilesState;
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Center, ScrollArea, Text, Stack } from '@mantine/core';
|
import { Center, ScrollArea, Text, Stack } from '@mantine/core';
|
||||||
import CloudIcon from '@mui/icons-material/Cloud';
|
import CloudIcon from '@mui/icons-material/Cloud';
|
||||||
import HistoryIcon from '@mui/icons-material/History';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import FileListItem from './FileListItem';
|
import FileListItem from './FileListItem';
|
||||||
import FileHistoryGroup from './FileHistoryGroup';
|
import FileHistoryGroup from './FileHistoryGroup';
|
||||||
|
import EmptyFilesState from './EmptyFilesState';
|
||||||
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
import { useFileManagerContext } from '../../contexts/FileManagerContext';
|
||||||
|
|
||||||
interface FileListAreaProps {
|
interface FileListAreaProps {
|
||||||
@ -29,6 +29,7 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
|||||||
onFileDoubleClick,
|
onFileDoubleClick,
|
||||||
onDownloadSingle,
|
onDownloadSingle,
|
||||||
isFileSupported,
|
isFileSupported,
|
||||||
|
isLoading,
|
||||||
} = useFileManagerContext();
|
} = useFileManagerContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -43,15 +44,11 @@ const FileListArea: React.FC<FileListAreaProps> = ({
|
|||||||
scrollbarSize={8}
|
scrollbarSize={8}
|
||||||
>
|
>
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
{recentFiles.length === 0 ? (
|
{recentFiles.length === 0 && !isLoading ? (
|
||||||
|
<EmptyFilesState />
|
||||||
|
) : recentFiles.length === 0 && isLoading ? (
|
||||||
<Center style={{ height: '12.5rem' }}>
|
<Center style={{ height: '12.5rem' }}>
|
||||||
<Stack align="center" gap="sm">
|
<Text c="dimmed" ta="center">{t('fileManager.loadingFiles', 'Loading files...')}</Text>
|
||||||
<HistoryIcon style={{ fontSize: '3rem', color: 'var(--mantine-color-gray-5)' }} />
|
|
||||||
<Text c="dimmed" ta="center">{t('fileManager.noRecentFiles', 'No recent files')}</Text>
|
|
||||||
<Text size="xs" c="dimmed" ta="center" style={{ opacity: 0.7 }}>
|
|
||||||
{t('fileManager.dropFilesHint', 'Drop files anywhere to upload')}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
filteredFiles.map((file, index) => {
|
filteredFiles.map((file, index) => {
|
||||||
|
|||||||
@ -9,7 +9,6 @@ const HiddenFileInput: React.FC = () => {
|
|||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
multiple={true}
|
multiple={true}
|
||||||
accept={["*/*"] as any}
|
|
||||||
onChange={onFileInputChange}
|
onChange={onFileInputChange}
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
data-testid="file-input"
|
data-testid="file-input"
|
||||||
|
|||||||
@ -41,7 +41,7 @@ const LandingPage = () => {
|
|||||||
{/* White PDF Page Background */}
|
{/* White PDF Page Background */}
|
||||||
<Dropzone
|
<Dropzone
|
||||||
onDrop={handleFileDrop}
|
onDrop={handleFileDrop}
|
||||||
accept={["application/pdf", "application/zip", "application/x-zip-compressed"]}
|
accept={["*/*"]}
|
||||||
multiple={true}
|
multiple={true}
|
||||||
className="w-4/5 flex items-center justify-center h-[95%]"
|
className="w-4/5 flex items-center justify-center h-[95%]"
|
||||||
style={{
|
style={{
|
||||||
@ -178,7 +178,7 @@ const LandingPage = () => {
|
|||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
accept=".pdf,.zip"
|
accept="*/*"
|
||||||
onChange={handleFileSelect}
|
onChange={handleFileSelect}
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -18,6 +18,7 @@ interface FileManagerContextValue {
|
|||||||
expandedFileIds: Set<FileId>;
|
expandedFileIds: Set<FileId>;
|
||||||
fileGroups: Map<FileId, StirlingFileStub[]>;
|
fileGroups: Map<FileId, StirlingFileStub[]>;
|
||||||
loadedHistoryFiles: Map<FileId, StirlingFileStub[]>;
|
loadedHistoryFiles: Map<FileId, StirlingFileStub[]>;
|
||||||
|
isLoading: boolean;
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
onSourceChange: (source: 'recent' | 'local' | 'drive') => void;
|
onSourceChange: (source: 'recent' | 'local' | 'drive') => void;
|
||||||
@ -58,6 +59,7 @@ interface FileManagerProviderProps {
|
|||||||
onFileRemove: (index: number) => void;
|
onFileRemove: (index: number) => void;
|
||||||
modalHeight: string;
|
modalHeight: string;
|
||||||
refreshRecentFiles: () => Promise<void>;
|
refreshRecentFiles: () => Promise<void>;
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
||||||
@ -71,6 +73,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
onFileRemove,
|
onFileRemove,
|
||||||
modalHeight,
|
modalHeight,
|
||||||
refreshRecentFiles,
|
refreshRecentFiles,
|
||||||
|
isLoading,
|
||||||
}) => {
|
}) => {
|
||||||
const [activeSource, setActiveSource] = useState<'recent' | 'local' | 'drive'>('recent');
|
const [activeSource, setActiveSource] = useState<'recent' | 'local' | 'drive'>('recent');
|
||||||
const [selectedFileIds, setSelectedFileIds] = useState<FileId[]>([]);
|
const [selectedFileIds, setSelectedFileIds] = useState<FileId[]>([]);
|
||||||
@ -574,6 +577,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
expandedFileIds,
|
expandedFileIds,
|
||||||
fileGroups,
|
fileGroups,
|
||||||
loadedHistoryFiles,
|
loadedHistoryFiles,
|
||||||
|
isLoading,
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
onSourceChange: handleSourceChange,
|
onSourceChange: handleSourceChange,
|
||||||
@ -607,6 +611,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
|||||||
expandedFileIds,
|
expandedFileIds,
|
||||||
fileGroups,
|
fileGroups,
|
||||||
loadedHistoryFiles,
|
loadedHistoryFiles,
|
||||||
|
isLoading,
|
||||||
handleSourceChange,
|
handleSourceChange,
|
||||||
handleLocalFileClick,
|
handleLocalFileClick,
|
||||||
handleFileSelect,
|
handleFileSelect,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user