feat: add per-format compression controls to the frontend

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 23:30:53 +02:00
co-authored by Claude Sonnet 5
parent 4cb9d76176
commit 44563edfcf
2 changed files with 87 additions and 12 deletions
+84 -12
View File
@@ -3,10 +3,25 @@ import { fetchFormats, uploadFiles } from './api.js';
import { FileCard } from './FileCard.jsx';
import './App.css';
const DEFAULT_QUALITY = {
jpg: 80,
jpeg: 80,
webp: 80,
avif: 50,
tiff: 80,
png: 6,
};
const QUALITY_FORMATS = ['jpg', 'jpeg', 'webp', 'avif', 'tiff'];
function extensionOf(fileName) {
return fileName.split('.').pop().toLowerCase();
}
function defaultQualityFor(targetFormat) {
return DEFAULT_QUALITY[targetFormat] ?? null;
}
export default function App() {
const [pendingFiles, setPendingFiles] = useState([]);
const [submittedJobs, setSubmittedJobs] = useState([]);
@@ -16,14 +31,23 @@ export default function App() {
const withTargets = await Promise.all(
files.map(async (file) => {
const targets = await fetchFormats(extensionOf(file.name));
return { file, targets, targetFormat: targets[0] ?? null };
const targetFormat = targets[0] ?? null;
return { file, targets, targetFormat, quality: defaultQualityFor(targetFormat) };
})
);
setPendingFiles(withTargets);
}
function updateTargetFormat(index, targetFormat) {
setPendingFiles((current) => current.map((item, i) => (i === index ? { ...item, targetFormat } : item)));
setPendingFiles((current) =>
current.map((item, i) =>
i === index ? { ...item, targetFormat, quality: defaultQualityFor(targetFormat) } : item
)
);
}
function updateQuality(index, quality) {
setPendingFiles((current) => current.map((item, i) => (i === index ? { ...item, quality } : item)));
}
async function handleConvert() {
@@ -46,16 +70,64 @@ export default function App() {
<li key={`${item.file.name}-${index}`}>
{item.file.name}
{item.targets.length > 0 ? (
<select
value={item.targetFormat ?? ''}
onChange={(event) => updateTargetFormat(index, event.target.value)}
>
{item.targets.map((target) => (
<option key={target} value={target}>
{target}
</option>
))}
</select>
<>
<select
value={item.targetFormat ?? ''}
onChange={(event) => updateTargetFormat(index, event.target.value)}
>
{item.targets.map((target) => (
<option key={target} value={target}>
{target}
</option>
))}
</select>
{QUALITY_FORMATS.includes(item.targetFormat) && (
<label>
Qualité ({item.quality})
<input
type="range"
min="1"
max="100"
value={item.quality}
onChange={(event) => updateQuality(index, Number(event.target.value))}
/>
</label>
)}
{item.targetFormat === 'png' && (
<label>
Compression ({item.quality})
<input
type="range"
min="0"
max="9"
value={item.quality}
onChange={(event) => updateQuality(index, Number(event.target.value))}
/>
</label>
)}
{item.targetFormat === 'pdf' && (
<label>
<input
type="checkbox"
checked={item.quality !== null}
onChange={(event) => updateQuality(index, event.target.checked ? 90 : null)}
/>
Compresser en JPEG
{item.quality !== null && (
<input
type="range"
min="1"
max="100"
value={item.quality}
onChange={(event) => updateQuality(index, Number(event.target.value))}
/>
)}
</label>
)}
</>
) : (
<span className="error">Format non supporté</span>
)}
+3
View File
@@ -7,11 +7,14 @@ export async function fetchFormats(source) {
export async function uploadFiles(items) {
const formData = new FormData();
const targetFormats = [];
const qualities = [];
for (const item of items) {
formData.append('files', item.file);
targetFormats.push(item.targetFormat);
qualities.push(item.quality ?? null);
}
formData.append('targetFormats', JSON.stringify(targetFormats));
formData.append('qualities', JSON.stringify(qualities));
const response = await fetch('/api/jobs', { method: 'POST', body: formData });
const data = await response.json();