forked from 77media/video-flow
298 lines
11 KiB
TypeScript
298 lines
11 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 '@/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 [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-6"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<div className="space-y-6">
|
|
{/* 工作模式 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Mode</label>
|
|
{renderDropdown('mode', 'Mode', modeOptions, selectedMode, setSelectedMode)}
|
|
</div>
|
|
|
|
{/* 分辨率 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Resolution</label>
|
|
{renderDropdown('resolution', 'Resolution', resolutionOptions, selectedResolution, setSelectedResolution)}
|
|
</div>
|
|
|
|
{/* 叠加效果 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Overlay Preset</label>
|
|
{renderDropdown('overlay', 'Overlay Preset', overlayOptions, selectedOverlay, setSelectedOverlay)}
|
|
</div>
|
|
|
|
{/* 转场设定 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Transition Preset</label>
|
|
{renderDropdown('transition', 'Transition Preset', transitionOptions, selectedTransition, setSelectedTransition)}
|
|
</div>
|
|
|
|
{/* 字幕风格 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Subtitle Preset</label>
|
|
{renderDropdown('subtitle', 'Subtitle Preset', subtitleOptions, selectedSubtitle, setSelectedSubtitle)}
|
|
</div>
|
|
|
|
{/* 贴纸预设 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Sticker Preset</label>
|
|
{renderDropdown('watermark', 'Sticker Preset', watermarkOptions, selectedWatermark, setSelectedWatermark)}
|
|
</div>
|
|
|
|
{/* 文字效果 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Text Preset</label>
|
|
{renderDropdown('textEffect', 'Text Preset', textEffectOptions, selectedTextEffect, setSelectedTextEffect)}
|
|
</div>
|
|
|
|
{/* 音效主音量 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">SFX Master Volume</label>
|
|
<div className="flex items-center gap-4">
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max="100"
|
|
value={sfxVolume}
|
|
className="flex-1 h-1 rounded-full bg-white/10 cursor-pointer
|
|
[&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
|
|
[&::-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);
|
|
setSfxVolume(newValue);
|
|
onSettingChange?.('SFX Master Volume', newValue);
|
|
}}
|
|
/>
|
|
<span className="w-12 text-right text-sm">{sfxVolume}%</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 媒体音频主音量 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Media Audio Master Volume</label>
|
|
<div className="flex items-center gap-4">
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max="100"
|
|
value={mediaVolume}
|
|
className="flex-1 h-1 rounded-full bg-white/10 cursor-pointer
|
|
[&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
|
|
[&::-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);
|
|
setMediaVolume(newValue);
|
|
onSettingChange?.('Media Audio Master Volume', newValue);
|
|
}}
|
|
/>
|
|
<span className="w-12 text-right text-sm">{mediaVolume}%</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 音乐主音量 */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm text-white/70">Music Master Volume</label>
|
|
<div className="flex items-center gap-4">
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max="100"
|
|
value={musicVolume}
|
|
className="flex-1 h-1 rounded-full bg-white/10 cursor-pointer
|
|
[&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
|
|
[&::-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);
|
|
setMusicVolume(newValue);
|
|
onSettingChange?.('Music Master Volume', newValue);
|
|
}}
|
|
/>
|
|
<span className="w-12 text-right text-sm">{musicVolume}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|