diff --git a/frontend/src/FileCard.jsx b/frontend/src/FileCard.jsx index e7983cd..fb39337 100644 --- a/frontend/src/FileCard.jsx +++ b/frontend/src/FileCard.jsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { CircleNotch, DownloadSimple, CheckCircle, WarningCircle } from '@phosphor-icons/react'; +import { CircleNotch, DownloadSimple, CheckCircle, WarningCircle, X } from '@phosphor-icons/react'; import { fetchJobStatus, downloadUrl } from './api.js'; import { formatBytes } from './utils/formatBytes.js'; import { fileTypeMeta } from './utils/fileFamily.js'; @@ -9,7 +9,7 @@ function extensionOf(fileName) { return fileName.split('.').pop(); } -export function FileCard({ fileName, fileSize, targetFormat, jobId, initialError }) { +export function FileCard({ fileName, fileSize, targetFormat, jobId, initialError, onRemove }) { const { t } = useTranslation(); const [status, setStatus] = useState(initialError ? 'failed' : 'pending'); const [errorMessage, setErrorMessage] = useState(initialError ?? null); @@ -45,6 +45,14 @@ export function FileCard({ fileName, fileSize, targetFormat, jobId, initialError {fileName} {targetFormat && {targetFormat.toUpperCase()}} {formatBytes(fileSize)} + {(status === 'pending' || status === 'processing') && (
diff --git a/frontend/src/components/FileConfigCard.jsx b/frontend/src/components/FileConfigCard.jsx index a67272e..cbd8a57 100644 --- a/frontend/src/components/FileConfigCard.jsx +++ b/frontend/src/components/FileConfigCard.jsx @@ -1,3 +1,4 @@ +import { X } from '@phosphor-icons/react'; import { formatBytes } from '../utils/formatBytes.js'; import { fileTypeMeta } from '../utils/fileFamily.js'; @@ -32,7 +33,7 @@ function RangeField({ label, value, min, max, onChange }) { ); } -export function FileConfigCard({ item, index, t, onTargetFormatChange, onQualityChange, onIconSizeChange }) { +export function FileConfigCard({ item, index, t, onTargetFormatChange, onQualityChange, onIconSizeChange, onRemove }) { const { Icon, colorVar } = fileTypeMeta(extensionOf(item.file.name)); return ( @@ -43,6 +44,14 @@ export function FileConfigCard({ item, index, t, onTargetFormatChange, onQuality {item.file.name} {formatBytes(item.file.size)} +
{item.targets.length > 0 ? ( diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index ca480e0..d98557c 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -1,5 +1,8 @@ { "brand": "Ombrora Convert", + "common": { + "remove": "Remove" + }, "nav": { "switchToEnglish": "English", "switchToFrench": "Français", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index f0e12bf..b78cccd 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -1,5 +1,8 @@ { "brand": "Ombrora Convert", + "common": { + "remove": "Supprimer" + }, "nav": { "switchToEnglish": "English", "switchToFrench": "Français", diff --git a/frontend/src/pages/HomePage.jsx b/frontend/src/pages/HomePage.jsx index e9e1112..1447f01 100644 --- a/frontend/src/pages/HomePage.jsx +++ b/frontend/src/pages/HomePage.jsx @@ -52,7 +52,7 @@ export function HomePage() { }; }) ); - setPendingFiles(withTargets); + setPendingFiles((current) => [...current, ...withTargets]); } function updateTargetFormat(index, targetFormat) { @@ -78,6 +78,14 @@ export function HomePage() { 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); @@ -114,6 +122,7 @@ export function HomePage() { onTargetFormatChange={updateTargetFormat} onQualityChange={updateQuality} onIconSizeChange={updateIconSize} + onRemove={removePendingFile} /> ))} @@ -132,6 +141,7 @@ export function HomePage() { fileSize={job.size} targetFormat={job.targetFormat} jobId={job.id} + onRemove={() => removeSubmittedJob(index)} /> ) : ( removeSubmittedJob(index)} /> ) )} diff --git a/frontend/src/styles/home.css b/frontend/src/styles/home.css index 511b75c..bb616eb 100644 --- a/frontend/src/styles/home.css +++ b/frontend/src/styles/home.css @@ -215,6 +215,28 @@ white-space: nowrap; } +.file-remove-button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + flex-shrink: 0; + border: none; + border-radius: 8px; + background: transparent; + color: var(--color-foreground); + opacity: 0.5; + cursor: pointer; + transition: opacity 150ms ease, background 150ms ease, color 150ms ease; +} + +.file-remove-button:hover { + opacity: 1; + color: var(--color-destructive); + background: color-mix(in srgb, var(--color-destructive) 12%, transparent); +} + .file-config-controls { display: flex; flex-direction: column;