feat: add per-format compression controls to the frontend
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+74
-2
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user