video_translate/src/components/UploadScreen.tsx
2026-03-18 11:42:00 +08:00

174 lines
7.7 KiB
TypeScript

import React, { useState } from 'react';
import { Upload, CheckCircle2, Circle } from 'lucide-react';
import TrimModal from './TrimModal';
const LANGUAGES = [
{ code: 'ar', name: 'Arabic', group: 'A' },
{ code: 'zh', name: 'Chinese', group: 'C' },
{ code: 'yue', name: 'Cantonese', group: 'C' },
{ code: 'cs', name: 'Czech', group: 'C' },
{ code: 'zh-TW', name: 'Traditional Chinese', group: 'T' },
{ code: 'th', name: 'Thai', group: 'T' },
{ code: 'tr', name: 'Turkey', group: 'T' },
{ code: 'nl', name: 'Dutch', group: 'D' },
{ code: 'en', name: 'English', group: 'E' },
{ code: 'ru', name: 'Russian', group: 'R' },
{ code: 'ro', name: 'Romanian', group: 'R' },
{ code: 'ja', name: 'Japanese', group: 'J' },
{ code: 'ko', name: 'Korean', group: 'K' },
{ code: 'ms', name: 'Malay', group: 'M' },
{ code: 'fr', name: 'French', group: 'F' },
];
export default function UploadScreen({ onUpload }: { onUpload: (file: File, lang: string, startTime?: number, endTime?: number) => void }) {
const [mode, setMode] = useState<'editing' | 'simple'>('editing');
const [selectedLang, setSelectedLang] = useState('en');
const [showTrimModal, setShowTrimModal] = useState(false);
const [tempFile, setTempFile] = useState<File | null>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setTempFile(e.target.files[0]);
setShowTrimModal(true);
}
};
const handleTrimConfirm = (file: File, startTime: number, endTime: number) => {
setShowTrimModal(false);
const langName = LANGUAGES.find(l => l.code === selectedLang)?.name || 'English';
onUpload(file, langName, startTime, endTime);
};
return (
<div className="max-w-6xl mx-auto p-8 flex gap-8 h-screen items-center">
{/* Left: Upload Area */}
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-200 p-8 flex flex-col items-center justify-center min-h-[400px]">
<div className="w-full h-full border-2 border-dashed border-gray-300 rounded-lg flex flex-col items-center justify-center bg-gray-50 relative">
<input
type="file"
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
accept="video/mp4,video/quicktime,video/webm"
onChange={handleFileChange}
/>
<Upload className="w-16 h-16 text-gray-400 mb-4" />
<p className="text-gray-600 mb-6">Click to upload or drag files here</p>
<button className="bg-[#52c41a] hover:bg-[#46a616] text-white px-8 py-3 rounded-md font-medium flex items-center gap-2 transition-colors w-full max-w-md justify-center pointer-events-none">
<Upload className="w-5 h-5" />
Upload Video
</button>
</div>
<p className="text-sm text-gray-500 mt-4 w-full text-left">
Supported formats: MP4/MOV/WEBM. Maximum file size is 500MB.
</p>
</div>
{/* Right: Settings */}
<div className="w-[400px] flex flex-col gap-6">
{/* Mode Selection */}
<div className="flex gap-4">
<div
className={`flex-1 p-4 rounded-lg border-2 cursor-pointer transition-colors ${
mode === 'editing' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-blue-200'
}`}
onClick={() => setMode('editing')}
>
<div className="flex items-center gap-2 mb-1">
{mode === 'editing' ? (
<CheckCircle2 className="w-5 h-5 text-blue-500" />
) : (
<Circle className="w-5 h-5 text-gray-300" />
)}
<span className="font-semibold text-gray-800">Editing Mode</span>
</div>
<p className="text-xs text-gray-500 ml-7">Supports secondary editing and more precise translation</p>
</div>
<div
className={`flex-1 p-4 rounded-lg border-2 cursor-pointer transition-colors ${
mode === 'simple' ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-blue-200'
}`}
onClick={() => setMode('simple')}
>
<div className="flex items-center gap-2 mb-1">
{mode === 'simple' ? (
<CheckCircle2 className="w-5 h-5 text-blue-500" />
) : (
<Circle className="w-5 h-5 text-gray-300" />
)}
<span className="font-semibold text-gray-800">Simple Mode</span>
</div>
<p className="text-xs text-gray-500 ml-7">One-click video translation for beginners</p>
</div>
</div>
{/* Language Selection */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 flex-1 flex flex-col">
<h3 className="font-semibold text-gray-800 mb-1">Select Translation Language</h3>
<p className="text-xs text-gray-500 mb-4">AI detect video source language automatically.</p>
{/* Alphabet Tabs */}
<div className="flex gap-4 border-b border-gray-100 pb-2 mb-4 text-sm text-gray-500 overflow-x-auto">
<button className="font-medium text-blue-600 border-b-2 border-blue-600 pb-2 -mb-[9px]">Popular</button>
<button className="hover:text-gray-800">ABC</button>
<button className="hover:text-gray-800">DEF</button>
<button className="hover:text-gray-800">GHI</button>
<button className="hover:text-gray-800">JKL</button>
<button className="hover:text-gray-800">MN</button>
<button className="hover:text-gray-800">OPQ</button>
<button className="hover:text-gray-800">RST</button>
<button className="hover:text-gray-800">UVW</button>
<button className="hover:text-gray-800">XYZ</button>
</div>
{/* Language List */}
<div className="flex-1 overflow-y-auto pr-2 custom-scrollbar">
{['A', 'C', 'T', 'D', 'E', 'R', 'J', 'K', 'M', 'F'].map((letter) => (
<div key={letter} className="flex border-b border-gray-100 py-3 last:border-0">
<div className="w-8 text-green-600 font-medium">{letter}</div>
<div className="flex flex-wrap gap-x-6 gap-y-2 flex-1">
{LANGUAGES.filter((l) => l.group === letter).map((lang) => (
<button
key={lang.code}
className={`text-sm hover:text-blue-600 transition-colors ${
selectedLang === lang.code
? 'bg-green-600 text-white px-2 py-0.5 rounded'
: lang.code === 'zh' || lang.code === 'yue' || lang.code === 'ja' || lang.code === 'ko'
? 'text-orange-500'
: 'text-gray-700'
}`}
onClick={() => setSelectedLang(lang.code)}
>
{lang.name}
</button>
))}
</div>
</div>
))}
</div>
<button
className={`w-full py-3 rounded-md font-medium mt-4 transition-colors ${
tempFile
? 'bg-[#52c41a] hover:bg-[#46a616] text-white'
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
}`}
onClick={() => {
if (tempFile) setShowTrimModal(true);
}}
disabled={!tempFile}
>
Generate Translated Video
</button>
</div>
</div>
{showTrimModal && tempFile && (
<TrimModal
file={tempFile}
onClose={() => setShowTrimModal(false)}
onConfirm={handleTrimConfirm}
/>
)}
</div>
);
}