Files
convert/frontend/src/pages/HomePage.jsx
T

172 lines
5.2 KiB
React

import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { fetchFormats, uploadFiles } from '../api.js';
import { FileCard } from '../FileCard.jsx';
import { Dropzone } from '../components/Dropzone.jsx';
import { FileConfigCard } from '../components/FileConfigCard.jsx';
import { ReassuranceStrip } from '../components/ReassuranceStrip.jsx';
import { FormatsGrid } from '../components/FormatsGrid.jsx';
import { FormatMarquee } from '../components/FormatMarquee.jsx';
import { SeoHead } from '../components/SeoHead.jsx';
import { extensionOf } from '../utils/archiveExtensions.js';
import '../styles/home.css';
import '../styles/sections.css';
const DEFAULT_QUALITY = {
jpg: 80,
jpeg: 80,
webp: 80,
avif: 50,
tiff: 80,
png: 6,
zip: 6,
'tar.gz': 6,
'tar.bz2': 9,
'7z': 5,
'tar.7z': 5,
mp3: 192,
ogg: 192,
aac: 192,
m4a: 192,
};
const DEFAULT_ICON_SIZE = 256;
function defaultQualityFor(targetFormat) {
return DEFAULT_QUALITY[targetFormat] ?? null;
}
export function HomePage() {
const { lang } = useParams();
const { t } = useTranslation();
const [pendingFiles, setPendingFiles] = useState([]);
const [submittedJobs, setSubmittedJobs] = useState([]);
async function handleFilesSelected(fileList) {
const files = Array.from(fileList);
const withTargets = await Promise.all(
files.map(async (file) => {
const targets = await fetchFormats(extensionOf(file.name));
const targetFormat = targets[0] ?? null;
return {
file,
targets,
targetFormat,
quality: defaultQualityFor(targetFormat),
iconSize: targetFormat === 'ico' ? DEFAULT_ICON_SIZE : null,
};
})
);
setPendingFiles((current) => [...current, ...withTargets]);
}
function updateTargetFormat(index, targetFormat) {
setPendingFiles((current) =>
current.map((item, i) =>
i === index
? {
...item,
targetFormat,
quality: defaultQualityFor(targetFormat),
iconSize: targetFormat === 'ico' ? DEFAULT_ICON_SIZE : null,
}
: item
)
);
}
function updateQuality(index, quality) {
setPendingFiles((current) => current.map((item, i) => (i === index ? { ...item, quality } : item)));
}
function updateIconSize(index, iconSize) {
setPendingFiles((current) => current.map((item, i) => (i === index ? { ...item, iconSize } : item)));
}
function removePendingFile(index) {
setPendingFiles((current) => current.filter((_, i) => i !== index));
}
function removeSubmittedJob(index) {
setSubmittedJobs((current) => current.filter((_, i) => i !== index));
}
async function handleConvert() {
const validItems = pendingFiles.filter((item) => item.targetFormat);
const jobs = await uploadFiles(validItems);
const jobsWithSize = jobs.map((job, i) => ({
...job,
size: validItems[i].file.size,
targetFormat: validItems[i].targetFormat,
}));
setSubmittedJobs((current) => [...current, ...jobsWithSize]);
setPendingFiles([]);
}
return (
<>
<SeoHead lang={lang} />
<section className="hero">
<div className="hero-aurora" aria-hidden="true" />
<div className="hero-content">
<span className="hero-kicker">{t('hero.kicker')}</span>
<h1 className="hero-title">{t('hero.title')}</h1>
<p>{t('hero.subtitle')}</p>
<Dropzone label={t('hero.dropzoneLabel')} onFilesSelected={handleFilesSelected} />
<FormatMarquee />
{pendingFiles.length > 0 && (
<div className="pending-files">
<ul>
{pendingFiles.map((item, index) => (
<FileConfigCard
key={`${item.file.name}-${index}`}
item={item}
index={index}
t={t}
onTargetFormatChange={updateTargetFormat}
onQualityChange={updateQuality}
onIconSizeChange={updateIconSize}
onRemove={removePendingFile}
/>
))}
</ul>
<button className="convert-button" onClick={handleConvert}>
{t('hero.convert')}
</button>
</div>
)}
<ul className="job-list">
{submittedJobs.map((job, index) =>
job.id ? (
<FileCard
key={job.id}
fileName={job.file}
fileSize={job.size}
targetFormat={job.targetFormat}
jobId={job.id}
onRemove={() => removeSubmittedJob(index)}
/>
) : (
<FileCard
key={`${job.file}-${index}`}
fileName={job.file}
fileSize={job.size}
targetFormat={job.targetFormat}
initialError={job.error}
onRemove={() => removeSubmittedJob(index)}
/>
)
)}
</ul>
</div>
</section>
<ReassuranceStrip />
<FormatsGrid />
</>
);
}