mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-01-14 20:11:17 +01:00
Landing zones
This commit is contained in:
parent
85dedf4b28
commit
700b341faa
@ -3131,6 +3131,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",
|
||||||
@ -3166,7 +3169,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) => {
|
||||||
@ -147,6 +147,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>
|
||||||
|
|||||||
179
frontend/src/components/fileEditor/AddFileCard.tsx
Normal file
179
frontend/src/components/fileEditor/AddFileCard.tsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
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 = ".pdf,.zip",
|
||||||
|
multiple = true
|
||||||
|
}: AddFileCardProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { openFilesModal } = useFilesModalContext();
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
const [isUploadHover, setIsUploadHover] = useState(false);
|
||||||
|
|
||||||
|
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 = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prevent this card from interfering with drag and drop to parent Dropzone
|
||||||
|
const handleDragOver = (e: React.DragEvent) => {
|
||||||
|
// Don't prevent default - let the parent Dropzone handle it
|
||||||
|
// Don't stop propagation - let it bubble up
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (e: React.DragEvent) => {
|
||||||
|
// Don't prevent default or stop propagation
|
||||||
|
// Let the parent Dropzone handle the file drop
|
||||||
|
};
|
||||||
|
|
||||||
|
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`}
|
||||||
|
tabIndex={0}
|
||||||
|
role="region"
|
||||||
|
aria-label={t('fileEditor.addFiles', 'Add files')}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
>
|
||||||
|
{/* 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;
|
||||||
@ -304,4 +304,84 @@
|
|||||||
/* Light mode selected header stroke override */
|
/* Light mode selected header stroke override */
|
||||||
: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';
|
||||||
@ -405,6 +406,16 @@ const FileEditor = ({
|
|||||||
pointerEvents: 'auto'
|
pointerEvents: 'auto'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Add File Card - only show when files exist */}
|
||||||
|
{activeStirlingFileStubs.length > 0 && (
|
||||||
|
<AddFileCard
|
||||||
|
key="add-file-card"
|
||||||
|
onFileSelect={handleFileUpload}
|
||||||
|
accept=".pdf,.zip"
|
||||||
|
multiple={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{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">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) => {
|
||||||
|
|||||||
@ -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