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
+74 -2
View File
@@ -3,10 +3,25 @@ import { fetchFormats, uploadFiles } from './api.js';
import { FileCard } from './FileCard.jsx'; import { FileCard } from './FileCard.jsx';
import './App.css'; 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) { function extensionOf(fileName) {
return fileName.split('.').pop().toLowerCase(); return fileName.split('.').pop().toLowerCase();
} }
function defaultQualityFor(targetFormat) {
return DEFAULT_QUALITY[targetFormat] ?? null;
}
export default function App() { export default function App() {
const [pendingFiles, setPendingFiles] = useState([]); const [pendingFiles, setPendingFiles] = useState([]);
const [submittedJobs, setSubmittedJobs] = useState([]); const [submittedJobs, setSubmittedJobs] = useState([]);
@@ -16,14 +31,23 @@ export default function App() {
const withTargets = await Promise.all( const withTargets = await Promise.all(
files.map(async (file) => { files.map(async (file) => {
const targets = await fetchFormats(extensionOf(file.name)); 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); setPendingFiles(withTargets);
} }
function updateTargetFormat(index, targetFormat) { 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() { async function handleConvert() {
@@ -46,6 +70,7 @@ export default function App() {
<li key={`${item.file.name}-${index}`}> <li key={`${item.file.name}-${index}`}>
{item.file.name} {item.file.name}
{item.targets.length > 0 ? ( {item.targets.length > 0 ? (
<>
<select <select
value={item.targetFormat ?? ''} value={item.targetFormat ?? ''}
onChange={(event) => updateTargetFormat(index, event.target.value)} onChange={(event) => updateTargetFormat(index, event.target.value)}
@@ -56,6 +81,53 @@ export default function App() {
</option> </option>
))} ))}
</select> </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> <span className="error">Format non supporté</span>
)} )}
+3
View File
@@ -7,11 +7,14 @@ export async function fetchFormats(source) {
export async function uploadFiles(items) { export async function uploadFiles(items) {
const formData = new FormData(); const formData = new FormData();
const targetFormats = []; const targetFormats = [];
const qualities = [];
for (const item of items) { for (const item of items) {
formData.append('files', item.file); formData.append('files', item.file);
targetFormats.push(item.targetFormat); targetFormats.push(item.targetFormat);
qualities.push(item.quality ?? null);
} }
formData.append('targetFormats', JSON.stringify(targetFormats)); formData.append('targetFormats', JSON.stringify(targetFormats));
formData.append('qualities', JSON.stringify(qualities));
const response = await fetch('/api/jobs', { method: 'POST', body: formData }); const response = await fetch('/api/jobs', { method: 'POST', body: formData });
const data = await response.json(); const data = await response.json();