feat(frontend): add routing, layout, i18n wiring, and restyle the conversion tool
Migrates the existing upload/format-select/quality/convert logic from the old single-file App.jsx into a router shell (/ -> /fr//en/) with a shared Layout (header, language switcher, dark-mode toggle, footer). Tool logic itself is unchanged, only restyled into cards. Part of the Ombrora Convert redesign (Task 2/5).
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { useState } from 'react';
|
||||
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 '../styles/home.css';
|
||||
|
||||
const DEFAULT_QUALITY = {
|
||||
jpg: 80,
|
||||
jpeg: 80,
|
||||
webp: 80,
|
||||
avif: 50,
|
||||
tiff: 80,
|
||||
png: 6,
|
||||
};
|
||||
|
||||
const DEFAULT_ICON_SIZE = 256;
|
||||
|
||||
function extensionOf(fileName) {
|
||||
return fileName.split('.').pop().toLowerCase();
|
||||
}
|
||||
|
||||
function defaultQualityFor(targetFormat) {
|
||||
return DEFAULT_QUALITY[targetFormat] ?? null;
|
||||
}
|
||||
|
||||
export function HomePage() {
|
||||
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(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)));
|
||||
}
|
||||
|
||||
async function handleConvert() {
|
||||
const validItems = pendingFiles.filter((item) => item.targetFormat);
|
||||
const jobs = await uploadFiles(validItems);
|
||||
setSubmittedJobs((current) => [...current, ...jobs]);
|
||||
setPendingFiles([]);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="hero">
|
||||
<h1>{t('hero.title')}</h1>
|
||||
<p>{t('hero.subtitle')}</p>
|
||||
<Dropzone label={t('hero.dropzoneLabel')} onFilesSelected={handleFilesSelected} />
|
||||
|
||||
{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}
|
||||
/>
|
||||
))}
|
||||
</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} jobId={job.id} />
|
||||
) : (
|
||||
<FileCard key={`${job.file}-${index}`} fileName={job.file} initialError={job.error} />
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user