feat(seo): add conversion-page scaffolding with 2 seed pairs (jpg-to-png, pdf-to-docx)

This commit is contained in:
2026-08-02 10:32:53 +02:00
parent 3b53d82b3d
commit e6ab78de29
5 changed files with 361 additions and 93 deletions
+108
View File
@@ -0,0 +1,108 @@
import { useState } from 'react';
import { fetchFormats, uploadFiles } from '../api.js';
import { extensionOf } from '../utils/archiveExtensions.js';
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 useConverterWorkflow({ preferredTargetFormat } = {}) {
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 =
preferredTargetFormat && targets.includes(preferredTargetFormat)
? preferredTargetFormat
: 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 {
pendingFiles,
submittedJobs,
handleFilesSelected,
updateTargetFormat,
updateQuality,
updateIconSize,
removePendingFile,
removeSubmittedJob,
handleConvert,
};
}