feat: add file upload button to media library

- Added file upload button with multi-file support alongside existing paste functionality
- Implemented handleFileUpload function to process multiple image files sequentially
- Added file input reference and reset logic to allow repeated uploads
- Included upload progress indicator showing count of files being processed
- Added validation to skip non-image files with error messaging
- Enhanced UI with disabled state during upload to prevent concurrent
This commit is contained in:
Ender 2025-10-26 22:36:11 +01:00
parent 33354e655e
commit be6ce75a77

View File

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState, useRef } from 'react';
import { Box, Button, Stack, Typography, Paper, TextField, MenuItem } from '@mui/material'; import { Box, Button, Stack, Typography, Paper, TextField, MenuItem } from '@mui/material';
type MediaItem = { type MediaItem = {
@ -138,6 +138,42 @@ export default function MediaLibrary({
} catch {} } catch {}
}; };
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileUpload = async (files: FileList | null) => {
if (!files || files.length === 0) return;
setUploading(true);
setUploadingCount(files.length);
setError('');
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (!file.type.startsWith('image/')) {
setError(`Skipped ${file.name}: not an image`);
continue;
}
try {
const fd = new FormData();
fd.append('image', file, file.name);
const res = await fetch('/api/media/image', { method: 'POST', body: fd });
if (!res.ok) throw new Error(await res.text());
} catch (err: any) {
setError(err?.message || `Failed to upload ${file.name}`);
}
}
setUploading(false);
setUploadingCount(0);
await load();
// Reset file input
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
return ( return (
<Paper sx={{ p: 2 }}> <Paper sx={{ p: 2 }}>
<Stack direction={{ xs: 'column', sm: 'row' }} justifyContent="flex-end" alignItems={{ xs: 'stretch', sm: 'center' }} sx={{ mb: 2, gap: 1 }}> <Stack direction={{ xs: 'column', sm: 'row' }} justifyContent="flex-end" alignItems={{ xs: 'stretch', sm: 'center' }} sx={{ mb: 2, gap: 1 }}>
@ -155,6 +191,22 @@ export default function MediaLibrary({
<MenuItem value="name_asc">Name</MenuItem> <MenuItem value="name_asc">Name</MenuItem>
<MenuItem value="size_desc">Largest</MenuItem> <MenuItem value="size_desc">Largest</MenuItem>
</TextField> </TextField>
<input
ref={fileInputRef}
type="file"
accept="image/*"
multiple
style={{ display: 'none' }}
onChange={(e) => handleFileUpload(e.target.files)}
/>
<Button
size="small"
variant="contained"
onClick={() => fileInputRef.current?.click()}
disabled={uploading}
>
{uploading ? `Uploading ${uploadingCount}...` : 'Upload Images'}
</Button>
<Button size="small" onClick={load} disabled={loading}>Refresh</Button> <Button size="small" onClick={load} disabled={loading}>Refresh</Button>
<Typography variant="caption" sx={{ color: 'text.secondary', display: { xs: 'none', md: 'block' } }}> <Typography variant="caption" sx={{ color: 'text.secondary', display: { xs: 'none', md: 'block' } }}>
Tip: paste an image (Cmd/Ctrl+V) to upload{uploading ? ` — uploading ${uploadingCount}` : ''} Tip: paste an image (Cmd/Ctrl+V) to upload{uploading ? ` — uploading ${uploadingCount}` : ''}