forked from 77media/video-flow
统一创建入口:模板选择兼容H5、选中模板没有配置文本 不展示输入框
This commit is contained in:
parent
7ffb8b3297
commit
ae807f33e9
@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { X } from 'lucide-react';
|
import { X, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
import { useDeviceType } from '@/hooks/useDeviceType';
|
||||||
|
|
||||||
interface TemplatePreviewModalProps {
|
interface TemplatePreviewModalProps {
|
||||||
/** Control visibility */
|
/** Control visibility */
|
||||||
@ -23,6 +24,7 @@ interface TemplatePreviewModalProps {
|
|||||||
/**
|
/**
|
||||||
* Fullscreen, Tailwind-based template preview modal with video content.
|
* Fullscreen, Tailwind-based template preview modal with video content.
|
||||||
* Overlays title/description if provided and supports an optional primary action.
|
* Overlays title/description if provided and supports an optional primary action.
|
||||||
|
* Supports expand/collapse for long descriptions and H5 responsive design.
|
||||||
*/
|
*/
|
||||||
export function TemplatePreviewModal({
|
export function TemplatePreviewModal({
|
||||||
open,
|
open,
|
||||||
@ -31,27 +33,62 @@ export function TemplatePreviewModal({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
onPrimaryAction,
|
onPrimaryAction,
|
||||||
primaryLabel = 'Try it',
|
primaryLabel = 'Try it Free',
|
||||||
}: TemplatePreviewModalProps) {
|
}: TemplatePreviewModalProps) {
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const [showExpandButton, setShowExpandButton] = useState(false);
|
||||||
|
const descriptionRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { isMobile } = useDeviceType();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setIsReady(false);
|
setIsReady(false);
|
||||||
|
setIsExpanded(false);
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isReady && description && descriptionRef.current) {
|
||||||
|
const element = descriptionRef.current;
|
||||||
|
const isOverflowing = element.scrollHeight > element.clientHeight;
|
||||||
|
setShowExpandButton(isOverflowing);
|
||||||
|
}
|
||||||
|
}, [isReady, description]);
|
||||||
|
|
||||||
if (!open || !videoUrl) return null;
|
if (!open || !videoUrl) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<style jsx>{`
|
||||||
|
.description-scroll::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.description-scroll::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.description-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.description-scroll::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
<div
|
<div
|
||||||
data-alt="template-preview-modal"
|
data-alt="template-preview-modal"
|
||||||
className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4"
|
className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-alt="template-preview-modal-content"
|
data-alt="template-preview-modal-content"
|
||||||
className="relative w-[70vw] min-h-[40vw] rounded-lg overflow-hidden border border-white/30 bg-black shadow-2xl"
|
className={`
|
||||||
|
relative rounded-lg overflow-hidden border border-white/30 bg-black shadow-2xl
|
||||||
|
${isMobile
|
||||||
|
? 'w-[95vw] max-h-[90vh] mx-2'
|
||||||
|
: 'w-[70vw] min-h-[40vw]'
|
||||||
|
}
|
||||||
|
`}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
@ -69,50 +106,121 @@ export function TemplatePreviewModal({
|
|||||||
data-alt="template-preview-loading"
|
data-alt="template-preview-loading"
|
||||||
className="absolute inset-0 flex items-center justify-center"
|
className="absolute inset-0 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div className="h-10 w-10 rounded-full border-2 border-white/30 border-t-white/80 animate-spin" />
|
<div className={`rounded-full border-2 border-white/30 border-t-white/80 animate-spin ${isMobile ? 'h-8 w-8' : 'h-10 w-10'}`} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isReady && (title || description) && (
|
{isReady && title && (
|
||||||
<div
|
<div
|
||||||
data-alt="template-preview-header"
|
data-alt="template-preview-header"
|
||||||
className="absolute top-0 left-0 right-0 bg-gradient-to-b from-black/90 via-black/80 to-transparent px-6 py-8 text-left"
|
className={`
|
||||||
|
absolute top-0 left-0 right-0
|
||||||
|
bg-gradient-to-b from-black/90 via-black/80 to-transparent
|
||||||
|
text-left
|
||||||
|
${isMobile ? 'px-3 py-2' : 'px-6 py-8'}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
{title && (
|
<div className={`font-bold text-white/90 line-clamp-2 ${isMobile ? 'text-base' : 'text-2xl'}`}>
|
||||||
<div className="text-bold text-2xl text-white/90 line-clamp-2 mb-2">{title}</div>
|
{title}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isReady && onPrimaryAction && (
|
{isReady && (description || onPrimaryAction) && (
|
||||||
<div
|
<div
|
||||||
data-alt="template-preview-footer"
|
data-alt="template-preview-footer"
|
||||||
className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 via-black/80 to-transparent px-8 py-6 text-center"
|
className={`
|
||||||
|
absolute bottom-0 left-0 right-0
|
||||||
|
bg-gradient-to-t from-black/95 via-black/85 to-transparent
|
||||||
|
text-center
|
||||||
|
${isMobile ? 'px-3 py-2' : 'px-8 py-6'}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
{description && (
|
{description && (
|
||||||
<div className="text-base text-white/80 line-clamp-2 text-left">{description}</div>
|
<div className={`text-left ${isMobile ? 'mb-2' : 'mb-3'}`}>
|
||||||
)}
|
<div
|
||||||
|
ref={descriptionRef}
|
||||||
|
className={`
|
||||||
|
text-white/80 transition-all duration-300
|
||||||
|
${isMobile ? 'text-sm' : 'text-base'}
|
||||||
|
${isExpanded
|
||||||
|
? `${isMobile ? 'max-h-20' : 'max-h-40'} overflow-y-auto pr-2 description-scroll`
|
||||||
|
: 'line-clamp-2'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
style={isExpanded ? {
|
||||||
|
scrollbarWidth: 'thin',
|
||||||
|
scrollbarColor: 'rgba(255, 255, 255, 0.2) transparent'
|
||||||
|
} : undefined}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
{showExpandButton && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 mt-3 px-6 py-2 text-white hover:border-white hover:bg-white hover:text-slate-900"
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
className={`
|
||||||
|
inline-flex items-center gap-1 mt-1
|
||||||
|
text-white/60 hover:text-white/90
|
||||||
|
transition-colors
|
||||||
|
${isMobile ? 'text-xs' : 'text-sm'}
|
||||||
|
`}
|
||||||
|
data-alt="description-toggle"
|
||||||
|
>
|
||||||
|
{isExpanded ? (
|
||||||
|
<>
|
||||||
|
<span>Collapse</span>
|
||||||
|
<ChevronUp className={isMobile ? 'h-3 w-3' : 'h-4 w-4'} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span>Expand</span>
|
||||||
|
<ChevronDown className={isMobile ? 'h-3 w-3' : 'h-4 w-4'} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{onPrimaryAction && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`
|
||||||
|
inline-flex items-center justify-center
|
||||||
|
rounded-full border border-white/30 bg-white/10
|
||||||
|
text-white hover:border-white hover:bg-white hover:text-slate-900
|
||||||
|
transition-all duration-200
|
||||||
|
${isMobile ? 'px-5 py-2 text-sm' : 'px-6 py-2.5 text-base'}
|
||||||
|
`}
|
||||||
onClick={onPrimaryAction}
|
onClick={onPrimaryAction}
|
||||||
data-alt="template-preview-primary"
|
data-alt="template-preview-primary"
|
||||||
>
|
>
|
||||||
{primaryLabel}
|
{primaryLabel}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-alt="template-preview-modal-close"
|
data-alt="template-preview-modal-close"
|
||||||
className="absolute top-3 right-3 inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 p-2 text-white hover:border-white hover:bg-white hover:text-slate-900"
|
className={`
|
||||||
|
absolute inline-flex items-center justify-center
|
||||||
|
rounded-full border border-white/30 bg-white/10
|
||||||
|
text-white hover:border-white hover:bg-white hover:text-slate-900
|
||||||
|
transition-all duration-200
|
||||||
|
${isMobile
|
||||||
|
? 'top-2 right-2 p-1.5'
|
||||||
|
: 'top-3 right-3 p-2'
|
||||||
|
}
|
||||||
|
`}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className={isMobile ? 'h-3.5 w-3.5' : 'h-4 w-4'} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -63,6 +63,21 @@ export default function VideoCreationForm() {
|
|||||||
void getTemplateStoryList();
|
void getTemplateStoryList();
|
||||||
}, [getTemplateStoryList]);
|
}, [getTemplateStoryList]);
|
||||||
|
|
||||||
|
/** Clear inputText when currentTemplate changes */
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentTemplate) {
|
||||||
|
setInputText('');
|
||||||
|
}
|
||||||
|
}, [currentTemplate]);
|
||||||
|
|
||||||
|
/** Determine if input textarea should be shown based on selected template's freeInput */
|
||||||
|
const shouldShowInput = useMemo(() => {
|
||||||
|
if (!isTemplateSelected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isTemplateSelected.freeInput && isTemplateSelected.freeInput.length > 0;
|
||||||
|
}, [isTemplateSelected]);
|
||||||
|
|
||||||
const characterInputRef = useRef<HTMLInputElement>(null);
|
const characterInputRef = useRef<HTMLInputElement>(null);
|
||||||
const sceneInputRef = useRef<HTMLInputElement>(null);
|
const sceneInputRef = useRef<HTMLInputElement>(null);
|
||||||
const propInputRef = useRef<HTMLInputElement>(null);
|
const propInputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -350,6 +365,7 @@ export default function VideoCreationForm() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Text Input Area - Middle */}
|
{/* Text Input Area - Middle */}
|
||||||
|
{shouldShowInput && (
|
||||||
<div data-alt="text-input-wrapper" className="flex-1 px-4 py-4">
|
<div data-alt="text-input-wrapper" className="flex-1 px-4 py-4">
|
||||||
<textarea
|
<textarea
|
||||||
data-alt="main-text-input"
|
data-alt="main-text-input"
|
||||||
@ -359,6 +375,7 @@ export default function VideoCreationForm() {
|
|||||||
onChange={(e) => setInputText(e.target.value)}
|
onChange={(e) => setInputText(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Control Panel - Bottom */}
|
{/* Control Panel - Bottom */}
|
||||||
<div data-alt="control-panel" className="px-4 py-4 flex items-center justify-between gap-2">
|
<div data-alt="control-panel" className="px-4 py-4 flex items-center justify-between gap-2">
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export default function CreateVideo() {
|
|||||||
<div className='p-1 text-center text-2xl font-bold tracking-tight sm:text-3xl'>Transform any idea into a compelling video</div>
|
<div className='p-1 text-center text-2xl font-bold tracking-tight sm:text-3xl'>Transform any idea into a compelling video</div>
|
||||||
<div className='mx-auto w-full max-w-[600px] px-3 py-2 text-center text-xs text-gray-400 sm:px-16 sm:text-sm'>Generate professional videos from simple prompts. Browse community creations for inspiration, or start fresh with your own vision</div>
|
<div className='mx-auto w-full max-w-[600px] px-3 py-2 text-center text-xs text-gray-400 sm:px-16 sm:text-sm'>Generate professional videos from simple prompts. Browse community creations for inspiration, or start fresh with your own vision</div>
|
||||||
<div className='py-2'>
|
<div className='py-2'>
|
||||||
<div className='space-y-4 mx-auto w-full max-w-[900px] px-3 sm:px-16'>
|
<div className='space-y-4 mx-auto w-full max-w-[900px] px-3 sm:px-16 bg-[radial-gradient(ellipse_at_center,rgba(106,244,249,0.28)_0%,rgba(106,244,249,0.14)_35%,transparent_70%)]'>
|
||||||
<VideoCreationForm />
|
<VideoCreationForm />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user