fix: resolve QA report issues across routing, editor, i18n, and pipeline

- Add /tools/:toolId legacy redirect and catch-all 404 page (P1-12, P1-13)
- Add Shift+O dodge/burn/sponge cycle and Ctrl+Y redo shortcut (P1-3, P1-4)
- Fix Fit on Screen menu action to properly compute fit zoom (P1-8)
- Add filename input to editor export dialog (P1-2)
- Fix password validation mismatch: frontend now requires 8 chars (P1-11)
- Add license info to Settings About section (P1-9)
- Replace hardcoded strings in dropzone, files, pipeline with i18n keys (P1-17 to P1-25)
- Add 20+ missing i18n keys to all 21 locale files
- Translate Japanese editor.shapes and settings.aiFeatures sections (P1-19, P1-20)
- Fix RTL: use logical CSS properties in sidebar, files, app-layout (P2-24 to P2-26)
- Add single-file download button to pipeline results (P1-28)
- Fix compress step settings restoration on pipeline load (P1-27)
- Increase mobile nav touch targets to 44px minimum (P2-29)
This commit is contained in:
SnapOtter
2026-06-05 22:31:28 +08:00
parent 91e90b390e
commit 07e12754ba
35 changed files with 706 additions and 44 deletions
+18 -12
View File
@@ -2,6 +2,7 @@ import { TOOLS } from "@snapotter/shared";
import { FileImage, ImageIcon, Workflow } from "lucide-react";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "@/contexts/i18n-context";
import {
apiGetFileDetails,
formatHeaders,
@@ -74,6 +75,7 @@ interface FileDetailsProps {
}
export function FileDetails({ mobile = false }: FileDetailsProps) {
const { t } = useTranslation();
const { selectedFileId } = useFilesPageStore();
const setFiles = useFileStore((s) => s.setFiles);
const navigate = useNavigate();
@@ -139,11 +141,11 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
"flex flex-col items-center justify-center text-muted-foreground",
mobile
? "flex flex-col gap-4"
: "w-60 border-l border-border p-4 shrink-0 hidden lg:flex flex-col",
: "w-60 border-s border-border p-4 shrink-0 hidden lg:flex flex-col",
)}
>
<FileImage className="h-12 w-12 mb-3 opacity-30" />
<p className="text-sm">Select a file to view details</p>
<p className="text-sm">{t.files.selectFilePrompt}</p>
</div>
);
}
@@ -155,7 +157,7 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
"flex items-center justify-center",
mobile
? "flex flex-col gap-4"
: "w-60 border-l border-border p-4 shrink-0 hidden lg:flex flex-col",
: "w-60 border-s border-border p-4 shrink-0 hidden lg:flex flex-col",
)}
>
<div className="h-6 w-6 border-2 border-primary border-t-transparent rounded-full animate-spin" />
@@ -171,7 +173,7 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
"overflow-y-auto",
mobile
? "flex flex-col gap-4"
: "w-60 border-l border-border p-4 shrink-0 hidden lg:flex flex-col",
: "w-60 border-s border-border p-4 shrink-0 hidden lg:flex flex-col",
)}
>
{/* Thumbnail */}
@@ -187,24 +189,28 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
<div className="flex-1">
<div className="rounded-lg border border-border overflow-hidden">
<div className="bg-blue-500/10 border-b border-border px-3 py-2">
<h4 className="text-sm font-semibold text-blue-600 dark:text-blue-400">File Details</h4>
<h4 className="text-sm font-semibold text-blue-600 dark:text-blue-400">
{t.files.fileDetailsHeading}
</h4>
</div>
<div className="divide-y divide-border">
<DetailRow label="Name" value={details.originalName} />
<DetailRow label={t.files.name} value={details.originalName} />
<DetailRow
label="Format"
label={t.files.format}
value={details.mimeType.replace("image/", "").toUpperCase()}
/>
<DetailRow label="Size" value={formatSize(details.size)} />
<DetailRow label={t.files.size} value={formatSize(details.size)} />
<DetailRow
label="Dimensions"
label={t.files.dimensions}
value={details.width && details.height ? `${details.width} × ${details.height}` : "—"}
/>
<DetailRow label="Version" value={`V${details.version}`} />
<DetailRow label={t.files.version} value={`V${details.version}`} />
<DetailRow
label="Tools Used"
label={t.files.toolsUsed}
value={
details.toolChain.length > 0 ? details.toolChain.map(toolName).join(", ") : "None"
details.toolChain.length > 0
? details.toolChain.map(toolName).join(", ")
: t.files.none
}
/>
</div>