feat(frontend): add archive format family, compression control, and fixed double-extension parsing
Also adds a --color-family-archives CSS variable (light + dark) that the plan didn't call out explicitly but is needed for the new family tile/icon to render with a color instead of falling back silently. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
import { X } from '@phosphor-icons/react';
|
||||
import { formatBytes } from '../utils/formatBytes.js';
|
||||
import { fileTypeMeta } from '../utils/fileFamily.js';
|
||||
import { extensionOf } from '../utils/archiveExtensions.js';
|
||||
|
||||
const QUALITY_FORMATS = ['jpg', 'jpeg', 'webp', 'avif', 'tiff'];
|
||||
const ICON_SIZES = [16, 32, 48, 256, 512];
|
||||
const DEFAULT_ICON_SIZE = 256;
|
||||
|
||||
function extensionOf(fileName) {
|
||||
return fileName.split('.').pop();
|
||||
}
|
||||
const ARCHIVE_COMPRESSION_FORMATS = ['zip', 'tar.gz', 'tar.bz2', '7z', 'tar.7z'];
|
||||
|
||||
function rangeProgress(value, min, max) {
|
||||
return `${((value - min) / (max - min)) * 100}%`;
|
||||
@@ -93,6 +91,16 @@ export function FileConfigCard({ item, index, t, onTargetFormatChange, onQuality
|
||||
/>
|
||||
)}
|
||||
|
||||
{ARCHIVE_COMPRESSION_FORMATS.includes(item.targetFormat) && (
|
||||
<RangeField
|
||||
label={t('quality.compression')}
|
||||
value={item.quality}
|
||||
min={0}
|
||||
max={9}
|
||||
onChange={(value) => onQualityChange(index, value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{item.targetFormat === 'ico' && (
|
||||
<div className="chip-field">
|
||||
<span>{t('quality.iconSize')}</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image, FileText, TextAa, BookOpen } from '@phosphor-icons/react';
|
||||
import { Image, FileText, TextAa, BookOpen, Archive } from '@phosphor-icons/react';
|
||||
import { FORMAT_FAMILIES } from '../data/formats.js';
|
||||
|
||||
const FAMILY_ICONS = {
|
||||
@@ -7,6 +7,7 @@ const FAMILY_ICONS = {
|
||||
documents: FileText,
|
||||
fonts: TextAa,
|
||||
ebooks: BookOpen,
|
||||
archives: Archive,
|
||||
};
|
||||
|
||||
export function FormatsGrid() {
|
||||
|
||||
@@ -15,4 +15,8 @@ export const FORMAT_FAMILIES = [
|
||||
key: 'ebooks',
|
||||
formats: ['epub', 'fb2', 'lrf', 'mobi', 'pdb', 'rb', 'snb', 'tcr', 'azw3', 'pdf'],
|
||||
},
|
||||
{
|
||||
key: 'archives',
|
||||
formats: ['zip', 'tar', 'tar.gz', 'tar.bz2', '7z', 'tar.7z', 'rar'],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
--color-family-documents: var(--color-aurora-cyan);
|
||||
--color-family-fonts: #f5a524;
|
||||
--color-family-ebooks: #10b981;
|
||||
--color-family-archives: #64748b;
|
||||
|
||||
--font-sans: 'Plus Jakarta Sans', system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--font-display: 'Bricolage Grotesque', var(--font-sans);
|
||||
@@ -51,6 +52,7 @@
|
||||
--color-family-documents: var(--color-aurora-cyan);
|
||||
--color-family-fonts: #ffb84d;
|
||||
--color-family-ebooks: #34d399;
|
||||
--color-family-archives: #94a3b8;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
"images": "Images",
|
||||
"documents": "Documents",
|
||||
"fonts": "Fonts",
|
||||
"ebooks": "Ebooks"
|
||||
"ebooks": "Ebooks",
|
||||
"archives": "Archives"
|
||||
},
|
||||
"footer": {
|
||||
"rights": "Ombrora Convert — Online file conversion"
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
"images": "Images",
|
||||
"documents": "Documents",
|
||||
"fonts": "Polices",
|
||||
"ebooks": "Ebooks"
|
||||
"ebooks": "Ebooks",
|
||||
"archives": "Archives"
|
||||
},
|
||||
"footer": {
|
||||
"rights": "Ombrora Convert — Conversion de fichiers en ligne"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ReassuranceStrip } from '../components/ReassuranceStrip.jsx';
|
||||
import { FormatsGrid } from '../components/FormatsGrid.jsx';
|
||||
import { FormatMarquee } from '../components/FormatMarquee.jsx';
|
||||
import { SeoHead } from '../components/SeoHead.jsx';
|
||||
import { extensionOf } from '../utils/archiveExtensions.js';
|
||||
import '../styles/home.css';
|
||||
import '../styles/sections.css';
|
||||
|
||||
@@ -19,14 +20,15 @@ const DEFAULT_QUALITY = {
|
||||
avif: 50,
|
||||
tiff: 80,
|
||||
png: 6,
|
||||
zip: 6,
|
||||
'tar.gz': 6,
|
||||
'tar.bz2': 9,
|
||||
'7z': 5,
|
||||
'tar.7z': 5,
|
||||
};
|
||||
|
||||
const DEFAULT_ICON_SIZE = 256;
|
||||
|
||||
function extensionOf(fileName) {
|
||||
return fileName.split('.').pop().toLowerCase();
|
||||
}
|
||||
|
||||
function defaultQualityFor(targetFormat) {
|
||||
return DEFAULT_QUALITY[targetFormat] ?? null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const DOUBLE_EXTENSIONS = ['tar.gz', 'tar.bz2', 'tar.7z'];
|
||||
|
||||
export function extensionOf(filename) {
|
||||
const lower = filename.toLowerCase();
|
||||
const match = DOUBLE_EXTENSIONS.find((ext) => lower.endsWith(`.${ext}`));
|
||||
return match ?? filename.split('.').pop().toLowerCase();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Image, FileText, TextAa, BookOpen, File as FileIcon } from '@phosphor-icons/react';
|
||||
import { Image, FileText, TextAa, BookOpen, Archive, File as FileIcon } from '@phosphor-icons/react';
|
||||
import { FORMAT_FAMILIES } from '../data/formats.js';
|
||||
|
||||
const FAMILY_ICONS = {
|
||||
@@ -6,6 +6,7 @@ const FAMILY_ICONS = {
|
||||
documents: FileText,
|
||||
fonts: TextAa,
|
||||
ebooks: BookOpen,
|
||||
archives: Archive,
|
||||
};
|
||||
|
||||
export function familyForExtension(extension) {
|
||||
|
||||
Reference in New Issue
Block a user