forked from 77media/video-flow
194 lines
8.6 KiB
TypeScript
194 lines
8.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useRef } from 'react';
|
|
import { PhotoPreviewSection } from '../PhotoPreview';
|
|
import type { PhotoItem, PhotoType } from '../PhotoPreview/types';
|
|
import {
|
|
PlusOutlined,
|
|
UserOutlined,
|
|
CameraOutlined,
|
|
BulbOutlined,
|
|
ArrowRightOutlined,
|
|
} from '@ant-design/icons';
|
|
import { Dropdown, Menu } from 'antd';
|
|
import { ConfigPanel } from './ConfigPanel';
|
|
import { defaultConfig } from './config-options';
|
|
import type { ConfigOptions } from './config-options';
|
|
import { useDeviceType } from '@/hooks/useDeviceType';
|
|
|
|
export default function VideoCreationForm() {
|
|
const [photos, setPhotos] = useState<PhotoItem[]>([]);
|
|
const [inputText, setInputText] = useState('');
|
|
const [configOptions, setConfigOptions] = useState<ConfigOptions>(defaultConfig);
|
|
|
|
const { isMobile, isDesktop } = useDeviceType();
|
|
|
|
const characterInputRef = useRef<HTMLInputElement>(null);
|
|
const sceneInputRef = useRef<HTMLInputElement>(null);
|
|
const propInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
/** Handle photo deletion */
|
|
const handleDeletePhoto = (index: number) => {
|
|
setPhotos(prevPhotos => prevPhotos.filter((_, i) => i !== index));
|
|
};
|
|
|
|
/** Handle file upload */
|
|
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>, type: PhotoType) => {
|
|
const files = event.target.files;
|
|
if (!files || files.length === 0) return;
|
|
|
|
const newPhotos: PhotoItem[] = Array.from(files).map((file, index) => ({
|
|
url: URL.createObjectURL(file),
|
|
type,
|
|
id: `${type}-${Date.now()}-${index}`,
|
|
}));
|
|
|
|
setPhotos(prevPhotos => [...prevPhotos, ...newPhotos]);
|
|
event.target.value = '';
|
|
};
|
|
|
|
/** Handle configuration change */
|
|
const handleConfigChange = <K extends keyof ConfigOptions>(
|
|
key: K,
|
|
value: ConfigOptions[K]
|
|
) => {
|
|
setConfigOptions(prev => ({ ...prev, [key]: value }));
|
|
};
|
|
|
|
/** Handle video creation */
|
|
const handleCreate = () => {
|
|
console.log({
|
|
text: inputText,
|
|
photos,
|
|
config: configOptions,
|
|
});
|
|
// TODO: Implement video creation logic
|
|
};
|
|
|
|
return (
|
|
<div data-alt="video-creation-form" className="w-full h-full flex flex-col">
|
|
{/* Main Content Area with Border */}
|
|
<div
|
|
data-alt="content-container"
|
|
className="flex-1 border border-white/10 rounded-3xl bg-gradient-to-br from-[#1a1a1a]/50 to-[#0a0a0a]/50 backdrop-blur-sm overflow-hidden flex flex-col"
|
|
>
|
|
{/* Photo Preview Section - Top */}
|
|
{photos.length > 0 && (
|
|
<div data-alt="photo-preview-wrapper" className="p-4 pb-0">
|
|
<PhotoPreviewSection
|
|
photos={photos}
|
|
onDelete={handleDeletePhoto}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Text Input Area - Middle */}
|
|
<div data-alt="text-input-wrapper" className="flex-1 px-4 py-4">
|
|
<textarea
|
|
data-alt="main-text-input"
|
|
className="w-full h-full bg-transparent text-gray-300 text-base placeholder-gray-500 resize-none outline-none border-none"
|
|
placeholder="Share a topic, idea, or instructions with Video Agent to produce a full avatar video"
|
|
value={inputText}
|
|
onChange={(e) => setInputText(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Control Panel - Bottom */}
|
|
<div data-alt="control-panel" className="px-4 py-4 flex items-center justify-between gap-2">
|
|
{/* Left Side - Upload and Options */}
|
|
<div data-alt="left-controls" className="flex items-center gap-2">
|
|
{/* Upload Button with Dropdown */}
|
|
<Dropdown
|
|
overlay={
|
|
<Menu className="bg-[#1a1a1a] border border-white/10">
|
|
<Menu.Item
|
|
key="character"
|
|
icon={<UserOutlined className="text-cyan-400" />}
|
|
onClick={() => characterInputRef.current?.click()}
|
|
>
|
|
<span className="text-gray-300">Character</span>
|
|
</Menu.Item>
|
|
<Menu.Item
|
|
key="scene"
|
|
icon={<CameraOutlined className="text-cyan-400" />}
|
|
onClick={() => sceneInputRef.current?.click()}
|
|
>
|
|
<span className="text-gray-300">Scene</span>
|
|
</Menu.Item>
|
|
<Menu.Item
|
|
key="prop"
|
|
icon={<BulbOutlined className="text-cyan-400" />}
|
|
onClick={() => propInputRef.current?.click()}
|
|
>
|
|
<span className="text-gray-300">Prop</span>
|
|
</Menu.Item>
|
|
</Menu>
|
|
}
|
|
trigger={['click']}
|
|
>
|
|
<button
|
|
data-alt="upload-button"
|
|
className="w-8 h-8 rounded-full border border-white/20 bg-transparent hover:bg-white/5 hover:border-cyan-400/60 transition-all duration-200 flex items-center justify-center text-gray-300 hover:text-cyan-400"
|
|
>
|
|
<PlusOutlined className="text-base font-bold" />
|
|
</button>
|
|
</Dropdown>
|
|
|
|
{/* Hidden file inputs */}
|
|
<input
|
|
ref={characterInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
multiple
|
|
className="hidden"
|
|
onChange={(e) => handleFileUpload(e, 'character')}
|
|
/>
|
|
<input
|
|
ref={sceneInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
multiple
|
|
className="hidden"
|
|
onChange={(e) => handleFileUpload(e, 'scene')}
|
|
/>
|
|
<input
|
|
ref={propInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
multiple
|
|
className="hidden"
|
|
onChange={(e) => handleFileUpload(e, 'prop')}
|
|
/>
|
|
|
|
{/* Mention Button */}
|
|
<button
|
|
data-alt="mention-button"
|
|
className="w-8 h-8 rounded-full border border-white/20 bg-transparent hover:bg-white/5 hover:border-cyan-400/60 transition-all duration-200 flex items-center justify-center text-gray-300 hover:text-cyan-400"
|
|
>
|
|
<span className="text-base font-bold">@</span>
|
|
</button>
|
|
|
|
{/* Configuration Panel */}
|
|
<ConfigPanel
|
|
configOptions={configOptions}
|
|
onConfigChange={handleConfigChange}
|
|
isMobile={isMobile}
|
|
isDesktop={isDesktop}
|
|
/>
|
|
</div>
|
|
|
|
{/* Right Side - Create Button */}
|
|
<button
|
|
data-alt="create-button"
|
|
className="w-8 h-8 rounded-full bg-black hover:bg-gray-900 border border-white/20 hover:border-cyan-400/60 transition-all duration-200 flex items-center justify-center text-white shadow-lg hover:shadow-cyan-400/20"
|
|
onClick={handleCreate}
|
|
>
|
|
<ArrowRightOutlined className="text-base font-bold" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|