diff --git a/frontend/src/components/FileConfigCard.jsx b/frontend/src/components/FileConfigCard.jsx index cbd8a57..253c7f2 100644 --- a/frontend/src/components/FileConfigCard.jsx +++ b/frontend/src/components/FileConfigCard.jsx @@ -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) && ( + onQualityChange(index, value)} + /> + )} + {item.targetFormat === 'ico' && (
{t('quality.iconSize')} diff --git a/frontend/src/components/FormatsGrid.jsx b/frontend/src/components/FormatsGrid.jsx index ebb76b2..fbb2d21 100644 --- a/frontend/src/components/FormatsGrid.jsx +++ b/frontend/src/components/FormatsGrid.jsx @@ -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() { diff --git a/frontend/src/data/formats.js b/frontend/src/data/formats.js index 747f8b9..af57c9c 100644 --- a/frontend/src/data/formats.js +++ b/frontend/src/data/formats.js @@ -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'], + }, ]; diff --git a/frontend/src/index.css b/frontend/src/index.css index 34ab1be..28ca9a1 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -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 { diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index d98557c..778a4ed 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -40,7 +40,8 @@ "images": "Images", "documents": "Documents", "fonts": "Fonts", - "ebooks": "Ebooks" + "ebooks": "Ebooks", + "archives": "Archives" }, "footer": { "rights": "Ombrora Convert — Online file conversion" diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index b78cccd..0f2cddf 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -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" diff --git a/frontend/src/pages/HomePage.jsx b/frontend/src/pages/HomePage.jsx index 1447f01..1c4b57b 100644 --- a/frontend/src/pages/HomePage.jsx +++ b/frontend/src/pages/HomePage.jsx @@ -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; } diff --git a/frontend/src/utils/archiveExtensions.js b/frontend/src/utils/archiveExtensions.js new file mode 100644 index 0000000..1b4990a --- /dev/null +++ b/frontend/src/utils/archiveExtensions.js @@ -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(); +} diff --git a/frontend/src/utils/fileFamily.js b/frontend/src/utils/fileFamily.js index 8477c1f..86f1328 100644 --- a/frontend/src/utils/fileFamily.js +++ b/frontend/src/utils/fileFamily.js @@ -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) {