forked from 77media/video-flow
319 lines
12 KiB
TypeScript
319 lines
12 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { motion, AnimatePresence } from 'framer-motion';
|
||
import { Check, ChevronDown, Volume2, Music, Mic, Radio } from 'lucide-react';
|
||
import { cn } from '@/public/lib/utils';
|
||
|
||
interface SettingsTabContentProps {
|
||
onSettingChange?: (key: string, value: any) => void;
|
||
}
|
||
|
||
interface SettingOption {
|
||
label: string;
|
||
value: string;
|
||
}
|
||
|
||
const modeOptions: SettingOption[] = [
|
||
{ label: 'Auto Mode', value: 'auto' },
|
||
{ label: 'Manual Mode', value: 'manual' },
|
||
];
|
||
|
||
const resolutionOptions: SettingOption[] = [
|
||
{ label: '720P', value: '720p' },
|
||
{ label: '1080P', value: '1080p' },
|
||
];
|
||
|
||
const overlayOptions: SettingOption[] = [
|
||
{ label: 'None', value: 'none' },
|
||
{ label: 'Light Leaks', value: 'light_leaks' },
|
||
{ label: 'Film Grain', value: 'film_grain' },
|
||
{ label: 'Dust', value: 'dust' },
|
||
];
|
||
|
||
const transitionOptions: SettingOption[] = [
|
||
{ label: 'None', value: 'none' },
|
||
{ label: 'Dynamic Cut', value: 'dynamic_cut' },
|
||
{ label: 'Zoom Pop', value: 'zoom_pop' },
|
||
{ label: 'Smooth Fade', value: 'smooth_fade' },
|
||
];
|
||
|
||
const subtitleOptions: SettingOption[] = [
|
||
{ label: 'None', value: 'none' },
|
||
{ label: 'Hormozi', value: 'hormozi' },
|
||
{ label: 'Castor', value: 'castor' },
|
||
{ label: 'Modern', value: 'modern' },
|
||
];
|
||
|
||
const watermarkOptions: SettingOption[] = [
|
||
{ label: 'None', value: 'none' },
|
||
{ label: 'Simple', value: 'simple' },
|
||
{ label: 'Branded', value: 'branded' },
|
||
];
|
||
|
||
const textEffectOptions: SettingOption[] = [
|
||
{ label: 'Minimal', value: 'minimal' },
|
||
{ label: 'Plain', value: 'plain' },
|
||
{ label: 'Dramatic', value: 'dramatic' },
|
||
{ label: 'Corporate', value: 'corporate' },
|
||
];
|
||
|
||
export function SettingsTabContent({ onSettingChange }: SettingsTabContentProps) {
|
||
const [selectedMode, setSelectedMode] = useState('auto');
|
||
const [selectedResolution, setSelectedResolution] = useState('1080p');
|
||
const [selectedOverlay, setSelectedOverlay] = useState('light_leaks');
|
||
const [selectedTransition, setSelectedTransition] = useState('none');
|
||
const [selectedSubtitle, setSelectedSubtitle] = useState('hormozi');
|
||
const [selectedWatermark, setSelectedWatermark] = useState('none');
|
||
const [selectedTextEffect, setSelectedTextEffect] = useState('minimal');
|
||
const [sfxVolume, setSfxVolume] = useState(75);
|
||
const [mediaVolume, setMediaVolume] = useState(75);
|
||
const [musicVolume, setMusicVolume] = useState(80);
|
||
const [voiceVolume, setVoiceVolume] = useState(80);
|
||
const [openDropdown, setOpenDropdown] = useState<string | null>(null);
|
||
|
||
const handleDropdownToggle = (key: string) => {
|
||
setOpenDropdown(openDropdown === key ? null : key);
|
||
};
|
||
|
||
const renderDropdown = (
|
||
key: string,
|
||
label: string,
|
||
options: SettingOption[],
|
||
value: string,
|
||
onChange: (value: string) => void
|
||
) => (
|
||
<div className="relative">
|
||
<motion.button
|
||
className={cn(
|
||
"w-full px-4 py-2 rounded-lg border text-left flex items-center justify-between",
|
||
openDropdown === key
|
||
? "border-blue-500 bg-blue-500/10"
|
||
: "border-white/10 hover:border-white/20"
|
||
)}
|
||
onClick={() => handleDropdownToggle(key)}
|
||
whileHover={{ scale: 1.01 }}
|
||
whileTap={{ scale: 0.99 }}
|
||
>
|
||
<span>{options.find(opt => opt.value === value)?.label || value}</span>
|
||
<motion.div
|
||
animate={{ rotate: openDropdown === key ? 180 : 0 }}
|
||
transition={{ duration: 0.2 }}
|
||
>
|
||
<ChevronDown className="w-4 h-4" />
|
||
</motion.div>
|
||
</motion.button>
|
||
|
||
<AnimatePresence>
|
||
{openDropdown === key && (
|
||
<motion.div
|
||
className="absolute z-10 w-full mt-1 py-1 rounded-lg border border-white/10 bg-black/90 backdrop-blur-xl"
|
||
initial={{ opacity: 0, y: -10 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -10 }}
|
||
transition={{ duration: 0.2 }}
|
||
>
|
||
{options.map((option) => (
|
||
<motion.button
|
||
key={option.value}
|
||
className={cn(
|
||
"w-full px-4 py-2 text-left flex items-center justify-between hover:bg-white/5",
|
||
value === option.value && "text-blue-500"
|
||
)}
|
||
onClick={() => {
|
||
onChange(option.value);
|
||
handleDropdownToggle(key);
|
||
onSettingChange?.(key, option.value);
|
||
}}
|
||
whileHover={{ x: 4 }}
|
||
>
|
||
{option.label}
|
||
{value === option.value && <Check className="w-4 h-4" />}
|
||
</motion.button>
|
||
))}
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
);
|
||
|
||
const renderVolumeSlider = (
|
||
icon: React.ReactNode,
|
||
label: string,
|
||
value: number,
|
||
onChange: (value: number) => void
|
||
) => (
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2 text-sm text-white/70">
|
||
{icon}
|
||
<span>{label}</span>
|
||
</div>
|
||
<div className="flex items-center gap-4">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="100"
|
||
value={value}
|
||
className="flex-1 h-2 rounded-full bg-white/10 cursor-pointer
|
||
[&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4
|
||
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-500
|
||
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:cursor-pointer
|
||
[&::-webkit-slider-thumb]:transition-all [&::-webkit-slider-thumb]:duration-200
|
||
[&::-webkit-slider-thumb]:hover:scale-110"
|
||
onChange={(e) => {
|
||
const newValue = parseInt(e.target.value);
|
||
onChange(newValue);
|
||
onSettingChange?.(label, newValue);
|
||
}}
|
||
/>
|
||
<span className="w-12 text-right text-sm">{value}%</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<motion.div
|
||
className="space-y-8 px-1"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.3 }}
|
||
>
|
||
{/* 基础设置组 */}
|
||
<div className="space-y-4">
|
||
<div className="flex items-center gap-2 pb-2 border-b border-white/10">
|
||
<div className="h-4 w-1 rounded-full bg-blue-500" />
|
||
<h3 className="text-sm font-medium text-white/90">基础设置</h3>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
{/* 工作模式 */}
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/70">工作模式</label>
|
||
{renderDropdown('mode', 'Mode', modeOptions, selectedMode, setSelectedMode)}
|
||
</div>
|
||
|
||
{/* 分辨率 */}
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/70">分辨率</label>
|
||
{renderDropdown('resolution', 'Resolution', resolutionOptions, selectedResolution, setSelectedResolution)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 视觉效果组 */}
|
||
<div className="space-y-4">
|
||
<div className="flex items-center gap-2 pb-2 border-b border-white/10">
|
||
<div className="h-4 w-1 rounded-full bg-emerald-500" />
|
||
<h3 className="text-sm font-medium text-white/90">视觉效果</h3>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
{/* 叠加效果 */}
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/70">叠加效果</label>
|
||
{renderDropdown('overlay', 'Overlay Preset', overlayOptions, selectedOverlay, setSelectedOverlay)}
|
||
</div>
|
||
|
||
{/* 转场设定 */}
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/70">转场效果</label>
|
||
{renderDropdown('transition', 'Transition Preset', transitionOptions, selectedTransition, setSelectedTransition)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 文字样式组 */}
|
||
<div className="space-y-4">
|
||
<div className="flex items-center gap-2 pb-2 border-b border-white/10">
|
||
<div className="h-4 w-1 rounded-full bg-purple-500" />
|
||
<h3 className="text-sm font-medium text-white/90">文字样式</h3>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
{/* 字幕风格 */}
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/70">字幕风格</label>
|
||
{renderDropdown('subtitle', 'Subtitle Preset', subtitleOptions, selectedSubtitle, setSelectedSubtitle)}
|
||
</div>
|
||
|
||
{/* 文字效果 */}
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/70">文字效果</label>
|
||
{renderDropdown('textEffect', 'Text Preset', textEffectOptions, selectedTextEffect, setSelectedTextEffect)}
|
||
</div>
|
||
|
||
{/* 贴纸预设 */}
|
||
<div className="space-y-2">
|
||
<label className="text-sm text-white/70">贴纸预设</label>
|
||
{renderDropdown('watermark', 'Sticker Preset', watermarkOptions, selectedWatermark, setSelectedWatermark)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 音频设置组 */}
|
||
<div className="space-y-4">
|
||
<div className="flex items-center gap-2 pb-2 border-b border-white/10">
|
||
<div className="h-4 w-1 rounded-full bg-amber-500" />
|
||
<h3 className="text-sm font-medium text-white/90">音频设置</h3>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 gap-6">
|
||
<div className="grid grid-cols-2 gap-6">
|
||
{/* 音效主音量 */}
|
||
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/[0.07] transition-colors duration-200">
|
||
{renderVolumeSlider(
|
||
<Volume2 className="w-4 h-4 text-amber-500" />,
|
||
"音效主音量",
|
||
sfxVolume,
|
||
setSfxVolume
|
||
)}
|
||
</div>
|
||
|
||
{/* 配音主音量 */}
|
||
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/[0.07] transition-colors duration-200">
|
||
{renderVolumeSlider(
|
||
<Mic className="w-4 h-4 text-amber-500" />,
|
||
"配音主音量",
|
||
voiceVolume,
|
||
setVoiceVolume
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-6">
|
||
{/* 媒体音频主音量 */}
|
||
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/[0.07] transition-colors duration-200">
|
||
{renderVolumeSlider(
|
||
<Radio className="w-4 h-4 text-amber-500" />,
|
||
"媒体音频主音量",
|
||
mediaVolume,
|
||
setMediaVolume
|
||
)}
|
||
</div>
|
||
|
||
{/* 音乐主音量 */}
|
||
<div className="p-4 rounded-xl bg-white/5 hover:bg-white/[0.07] transition-colors duration-200">
|
||
{renderVolumeSlider(
|
||
<Music className="w-4 h-4 text-amber-500" />,
|
||
"音乐主音量",
|
||
musicVolume,
|
||
setMusicVolume
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 音量混合提示 */}
|
||
<div className="flex items-center gap-3 px-4 py-3 rounded-lg bg-amber-500/10 border border-amber-500/20">
|
||
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-amber-500/20">
|
||
<Volume2 className="w-4 h-4 text-amber-500" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<p className="text-sm text-amber-500">音量混合提示</p>
|
||
<p className="text-xs text-amber-500/80 mt-0.5">
|
||
建议保持音效、配音、媒体音频和音乐之间的适当平衡,以获得最佳听觉体验
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
);
|
||
}
|