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
@@ -21,10 +21,12 @@ export function CompressControls({ settings: initialSettings, onChange }: Compre
const [targetSizeValue, setTargetSizeValue] = useState("");
const [sizeUnit, setSizeUnit] = useState<SizeUnit>("KB");
const initializedRef = useRef(false);
const prevSettingsKeyRef = useRef<string | null>(null);
useEffect(() => {
if (!initialSettings || initializedRef.current) return;
initializedRef.current = true;
if (!initialSettings) return;
const key = JSON.stringify(initialSettings);
if (prevSettingsKeyRef.current === key) return;
prevSettingsKeyRef.current = key;
if (initialSettings.mode != null) setMode(initialSettings.mode as CompressMode);
if (initialSettings.quality != null) setQuality(Number(initialSettings.quality));
if (initialSettings.targetSizeKb != null)
@@ -129,7 +129,7 @@ function SortableStep({
e.stopPropagation();
onRemove();
}}
title="Remove"
title={t.automate.removeStep}
className="p-1 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive"
>
<X className="h-4 w-4" />
@@ -159,6 +159,7 @@ export function PipelineBuilder({
onUpdateSettings,
onToggleStep,
}: PipelineBuilderProps) {
const { t } = useTranslation();
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
@@ -177,10 +178,8 @@ export function PipelineBuilder({
<div className="p-4 rounded-full bg-muted/50 mb-4">
<FileImage className="h-8 w-8 text-muted-foreground" />
</div>
<h3 className="text-sm font-medium text-foreground mb-1">No steps yet</h3>
<p className="text-sm text-muted-foreground max-w-[240px]">
Click tools from the palette to build your pipeline
</p>
<h3 className="text-sm font-medium text-foreground mb-1">{t.automate.noStepsHeading}</h3>
<p className="text-sm text-muted-foreground max-w-[240px]">{t.automate.addToolsPrompt}</p>
</div>
);
}
@@ -66,12 +66,16 @@ export function ToolPalette({ onAddStep, className }: ToolPaletteProps) {
return (
<div className={cn("flex flex-col h-full", className)}>
<div className="px-3 pt-3 pb-2 shrink-0">
<SearchBar value={search} onChange={setSearch} placeholder="Search tools..." />
<SearchBar
value={search}
onChange={setSearch}
placeholder={t.automate.searchToolsPlaceholder}
/>
</div>
<div className="flex-1 overflow-y-auto px-3 pb-3">
{availableTools.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-8">No tools found</p>
<p className="text-sm text-muted-foreground text-center py-8">{t.common.noToolsFound}</p>
) : isSearching ? (
<div className="space-y-1">
{availableTools.map((tool) => (