forked from 77media/video-flow
fix banner
This commit is contained in:
parent
9f77815ab5
commit
34e5eea684
@ -4,7 +4,9 @@ import { DashboardLayout } from '@/components/layout/dashboard-layout';
|
|||||||
export default function CreatePage() {
|
export default function CreatePage() {
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
|
<div className='w-full h-full overflow-y-auto pb-4'>
|
||||||
<CreateVideo />
|
<CreateVideo />
|
||||||
|
</div>
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
14
app/page.tsx
14
app/page.tsx
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useLayoutEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { TopBar } from "@/components/layout/top-bar";
|
import { TopBar } from "@/components/layout/top-bar";
|
||||||
import { HomePage2 } from "@/components/pages/home-page2";
|
import { HomePage2 } from "@/components/pages/home-page2";
|
||||||
@ -13,11 +13,17 @@ export default function Home() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { deviceType, isMobile, isTablet, isDesktop } = useDeviceType();
|
const { deviceType, isMobile, isTablet, isDesktop } = useDeviceType();
|
||||||
|
|
||||||
useEffect(() => {
|
const authed = isAuthenticated();
|
||||||
if (isAuthenticated()) {
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (authed) {
|
||||||
router.replace('/home');
|
router.replace('/home');
|
||||||
}
|
}
|
||||||
}, [router]);
|
}, [authed, router]);
|
||||||
|
|
||||||
|
if (authed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isMobile || isTablet ? (
|
{isMobile || isTablet ? (
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const FamousTemplate: React.FC<FamousTemplateProps> = ({ showTabs = true }) => {
|
|||||||
<section data-alt="famous-template" className="w-full">
|
<section data-alt="famous-template" className="w-full">
|
||||||
<div data-alt="famous-template-header" className={`mb-4 items-center ${isDesktop ? 'flex' : 'block'}`}>
|
<div data-alt="famous-template-header" className={`mb-4 items-center ${isDesktop ? 'flex' : 'block'}`}>
|
||||||
<h2 data-alt="famous-template-title" className="text-xl py-4 font-semibold text-white">
|
<h2 data-alt="famous-template-title" className="text-xl py-4 font-semibold text-white">
|
||||||
Hot Templates
|
Inspiration Lab
|
||||||
</h2>
|
</h2>
|
||||||
{showTabs && (
|
{showTabs && (
|
||||||
<div data-alt="template-tabs" className={`flex flex-wrap items-center gap-2 ${isDesktop ? 'ml-4' : 'ml-0'}`}>
|
<div data-alt="template-tabs" className={`flex flex-wrap items-center gap-2 ${isDesktop ? 'ml-4' : 'ml-0'}`}>
|
||||||
@ -86,15 +86,12 @@ const FamousTemplate: React.FC<FamousTemplateProps> = ({ showTabs = true }) => {
|
|||||||
className="relative h-40 group"
|
className="relative h-40 group"
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
const v = e.currentTarget.querySelector('video') as HTMLVideoElement | null
|
const v = e.currentTarget.querySelector('video') as HTMLVideoElement | null
|
||||||
// @ts-ignore
|
|
||||||
v?.play?.()
|
v?.play?.()
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
const v = e.currentTarget.querySelector('video') as HTMLVideoElement | null
|
const v = e.currentTarget.querySelector('video') as HTMLVideoElement | null
|
||||||
// @ts-ignore
|
|
||||||
v?.pause?.()
|
v?.pause?.()
|
||||||
if (v) {
|
if (v) {
|
||||||
// @ts-ignore
|
|
||||||
v.currentTime = 0
|
v.currentTime = 0
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -117,13 +114,13 @@ const FamousTemplate: React.FC<FamousTemplateProps> = ({ showTabs = true }) => {
|
|||||||
<div data-alt="template-name" className="text-base font-bold text-white truncate">{t.name}</div>
|
<div data-alt="template-name" className="text-base font-bold text-white truncate">{t.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-alt="template-video" className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-out">
|
<div data-alt="template-video" className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-out">
|
||||||
<video
|
{t.show_url && <video
|
||||||
src={t.show_url}
|
src={t.show_url}
|
||||||
className="w-full h-full object-cover object-center"
|
className="w-full h-full object-cover object-center"
|
||||||
playsInline
|
playsInline
|
||||||
muted
|
muted
|
||||||
preload="none"
|
preload="none"
|
||||||
/>
|
/>}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
data-alt="template-hover-overlay"
|
data-alt="template-hover-overlay"
|
||||||
@ -153,7 +150,7 @@ const FamousTemplate: React.FC<FamousTemplateProps> = ({ showTabs = true }) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-alt="try-this-button"
|
data-alt="try-this-button"
|
||||||
className="absolute top-2 left-1/2 -translate-x-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-out bg-white/40 text-white hover:bg-white hover:text-black text-slate-900 text-xs font-medium px-2 py-1 rounded-full border-white/60 shadow-sm"
|
className="absolute top-4 left-1/2 -translate-x-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-out bg-white/40 text-white hover:bg-white hover:text-black text-slate-900 text-xs font-medium px-4 py-2 rounded-full border-white/60 shadow-sm"
|
||||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const id = t.id || t.template_id
|
const id = t.id || t.template_id
|
||||||
|
|||||||
@ -1,266 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState, useRef } from "react";
|
|
||||||
import { fetchSettingByCode } from "@/api/serversetting";
|
|
||||||
import { X, ChevronUp, ChevronsDown } from "lucide-react";
|
|
||||||
import { ChatInputBox } from "@/components/ChatInputBox/ChatInputBox";
|
|
||||||
import { VideoCreationForm } from '@/components/pages/create-video/CreateInput';
|
import { VideoCreationForm } from '@/components/pages/create-video/CreateInput';
|
||||||
import { useDeviceType } from '@/hooks/useDeviceType';
|
import { useDeviceType } from '@/hooks/useDeviceType';
|
||||||
import { useAppSelector } from '@/lib/store/hooks';
|
|
||||||
|
|
||||||
export const HOME_BANNER_CODE = "homeBanner";
|
|
||||||
const HOME_BANNER_COLLAPSE_KEY = "homeBannerCollapsedDate";
|
|
||||||
|
|
||||||
/** CTA config for banner */
|
|
||||||
export interface HomeBannerCTAConfig {
|
|
||||||
label?: string;
|
|
||||||
href?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Configuration payload for `HomeBanner` */
|
|
||||||
export interface HomeBannerConfig {
|
|
||||||
eyebrow?: string;
|
|
||||||
title?: string;
|
|
||||||
subtitle?: string;
|
|
||||||
description?: string;
|
|
||||||
backgroundImage?: string;
|
|
||||||
cta?: HomeBannerCTAConfig;
|
|
||||||
ctaText?: string;
|
|
||||||
ctaLink?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the home banner with optional background image and CTA.
|
|
||||||
* Pulls configuration from server settings keyed by `HOME_BANNER_CODE`.
|
|
||||||
* @returns React.ReactElement | null
|
|
||||||
*/
|
|
||||||
export default function HomeBanner() {
|
export default function HomeBanner() {
|
||||||
const [config, setConfig] = useState<HomeBannerConfig | null>(null);
|
const { isDesktop } = useDeviceType();
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [isFlying, setIsFlying] = useState<boolean>(false);
|
|
||||||
const autoCollapseTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
||||||
const skipAutoCollapseRef = useRef<boolean>(false);
|
|
||||||
|
|
||||||
const { isMobile, isDesktop } = useDeviceType();
|
|
||||||
const selectedTemplateId = useAppSelector((state) => state.creationTemplate.selectedTemplateId);
|
|
||||||
|
|
||||||
/** Returns YYYY-MM-DD for user's local timezone */
|
|
||||||
const getLocalDateKey = () => {
|
|
||||||
const now = new Date();
|
|
||||||
const y = now.getFullYear();
|
|
||||||
const m = String(now.getMonth() + 1).padStart(2, '0');
|
|
||||||
const d = String(now.getDate()).padStart(2, '0');
|
|
||||||
return `${y}-${m}-${d}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDismiss = () => {
|
|
||||||
setIsFlying(true);
|
|
||||||
try {
|
|
||||||
localStorage.setItem(HOME_BANNER_COLLAPSE_KEY, getLocalDateKey());
|
|
||||||
} catch {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBannerClick = () => {
|
|
||||||
if (isFlying) {
|
|
||||||
setIsFlying(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize from persisted state: keep collapsed for the rest of the day
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
const saved = localStorage.getItem(HOME_BANNER_COLLAPSE_KEY);
|
|
||||||
if (saved && saved === getLocalDateKey()) {
|
|
||||||
setIsFlying(true);
|
|
||||||
skipAutoCollapseRef.current = true;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Auto collapse after mount (2s) unless already collapsed today
|
|
||||||
useEffect(() => {
|
|
||||||
if (skipAutoCollapseRef.current) return;
|
|
||||||
if (autoCollapseTimerRef.current) return;
|
|
||||||
autoCollapseTimerRef.current = setTimeout(() => {
|
|
||||||
setIsFlying(true);
|
|
||||||
try {
|
|
||||||
localStorage.setItem(HOME_BANNER_COLLAPSE_KEY, getLocalDateKey());
|
|
||||||
} catch {}
|
|
||||||
}, 2000);
|
|
||||||
return () => {
|
|
||||||
if (autoCollapseTimerRef.current) {
|
|
||||||
clearTimeout(autoCollapseTimerRef.current);
|
|
||||||
autoCollapseTimerRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Collapse banner when a template is selected/changed
|
|
||||||
useEffect(() => {
|
|
||||||
if (!selectedTemplateId) return;
|
|
||||||
setIsFlying(true);
|
|
||||||
try {
|
|
||||||
localStorage.setItem(HOME_BANNER_COLLAPSE_KEY, getLocalDateKey());
|
|
||||||
} catch {}
|
|
||||||
if (autoCollapseTimerRef.current) {
|
|
||||||
clearTimeout(autoCollapseTimerRef.current);
|
|
||||||
autoCollapseTimerRef.current = null;
|
|
||||||
}
|
|
||||||
}, [selectedTemplateId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let active = true;
|
|
||||||
|
|
||||||
async function load() {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const value = await fetchSettingByCode<HomeBannerConfig>(HOME_BANNER_CODE);
|
|
||||||
if (!active) return;
|
|
||||||
setConfig(value ?? null);
|
|
||||||
} catch (err) {
|
|
||||||
if (!active) return;
|
|
||||||
const message = err instanceof Error ? err.message : "Failed to load home banner";
|
|
||||||
setError(message);
|
|
||||||
setConfig(null);
|
|
||||||
} finally {
|
|
||||||
if (active) {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
load();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
active = false;
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (loading || error || !config) {
|
|
||||||
// Render nothing until configuration schema is finalized.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = typeof config.title === "string" ? config.title : undefined;
|
|
||||||
const subtitle = typeof config.subtitle === "string" ? config.subtitle : undefined;
|
|
||||||
const description = typeof config.description === "string" ? config.description : undefined;
|
|
||||||
const eyebrow = typeof config.eyebrow === "string" ? config.eyebrow : undefined;
|
|
||||||
const backgroundImage = typeof config.backgroundImage === "string" ? config.backgroundImage : undefined;
|
|
||||||
|
|
||||||
let ctaLabel: string | undefined;
|
|
||||||
let ctaHref: string | undefined;
|
|
||||||
|
|
||||||
if (config.cta && typeof config.cta === "object") {
|
|
||||||
const { label, href } = config.cta as Record<string, unknown>;
|
|
||||||
if (typeof label === "string") ctaLabel = label;
|
|
||||||
if (typeof href === "string") ctaHref = href;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support legacy field names until the API contract is confirmed.
|
|
||||||
if (!ctaLabel && typeof config.ctaText === "string") {
|
|
||||||
ctaLabel = config.ctaText;
|
|
||||||
}
|
|
||||||
if (!ctaHref && typeof config.ctaLink === "string") {
|
|
||||||
ctaHref = config.ctaLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasContent = Boolean(title || subtitle || description || ctaLabel || backgroundImage);
|
|
||||||
if (!hasContent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-alt="home-banner-wrapper" className="sticky top-0 z-50 w-full mx-auto p-0 overflow-hidden bg-gradient-to-b from-black/80 to-black/10">
|
<div data-alt="home-banner-wrapper" className="sticky top-0 z-50 w-full mx-auto p-0 overflow-hidden bg-gradient-to-b from-black/80 to-black/10">
|
||||||
{/* Banner overlay - stacked above */}
|
|
||||||
<section
|
|
||||||
data-alt="home-banner"
|
|
||||||
className={`absolute inset-0 z-10 isolate overflow-hidden rounded-3xl px-6 py-6 text-white border-2 border-transparent transition-all duration-400 ease-in-out ${
|
|
||||||
isFlying ? 'ring-2 ring-custom-blue ring-offset-0 [--tw-ring-color:theme(colors.custom-blue)] animate-ring-breath' : 'hover:border-custom-blue/50'
|
|
||||||
} ${
|
|
||||||
isFlying
|
|
||||||
? isDesktop ? "translate-x-[90%] -translate-y-[70%] scale-[0.85] opacity-95 rotate-3" : "translate-x-[80%] -translate-y-[70%] scale-[0.85] opacity-95 rotate-3"
|
|
||||||
: "translate-x-0 translate-y-0 scale-100 opacity-100 rotate-0"
|
|
||||||
}`}
|
|
||||||
aria-label="Home banner"
|
|
||||||
>
|
|
||||||
{/* 背景图层 - 完全铺满 */}
|
|
||||||
{backgroundImage ? (
|
|
||||||
<div data-alt="background-wrapper" className="absolute inset-0">
|
|
||||||
<img
|
|
||||||
data-alt="background-image"
|
|
||||||
src={backgroundImage}
|
|
||||||
className="h-full w-full object-cover block"
|
|
||||||
/>
|
|
||||||
<div data-alt="background-overlay" className="absolute inset-0 bg-black/40" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{/* Dismiss button */}
|
|
||||||
<div className={`absolute z-10 ${isDesktop ? 'right-4 top-4' : 'right-0 top-0'}`}>
|
|
||||||
<button
|
|
||||||
data-alt="banner-dismiss"
|
|
||||||
type="button"
|
|
||||||
onClick={handleDismiss}
|
|
||||||
className="text-white hover:bg-white/20 rounded-full p-2"
|
|
||||||
aria-label="Dismiss banner"
|
|
||||||
>
|
|
||||||
<ChevronUp className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
data-alt="banner-content"
|
|
||||||
className="relative flex flex-col items-center gap-4 text-center md:items-start md:text-left"
|
|
||||||
>
|
|
||||||
{eyebrow ? (
|
|
||||||
<span data-alt="banner-eyebrow" className="text-xs font-semibold uppercase tracking-[0.3em] text-white/70">
|
|
||||||
{eyebrow}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
{title ? (
|
|
||||||
<h1 data-alt="banner-title" className="text-3xl font-semibold leading-tight md:text-5xl">
|
|
||||||
{title}
|
|
||||||
</h1>
|
|
||||||
) : null}
|
|
||||||
{(subtitle && isDesktop) ? (
|
|
||||||
<p data-alt="banner-subtitle" className="text-lg text-white/80 md:text-xl">{subtitle}</p>
|
|
||||||
) : null}
|
|
||||||
{(description && isDesktop) ? (
|
|
||||||
<p data-alt="banner-description" className="max-w-2xl text-base text-white/70">{description}</p>
|
|
||||||
) : null}
|
|
||||||
{ctaLabel ? (
|
|
||||||
ctaHref ? (
|
|
||||||
<a
|
|
||||||
data-alt="cta-link"
|
|
||||||
href={ctaHref}
|
|
||||||
className="inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 px-6 py-2 text-sm font-medium text-white transition hover:border-white hover:bg-white hover:text-slate-900"
|
|
||||||
>
|
|
||||||
{ctaLabel}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
data-alt="cta-button"
|
|
||||||
type="button"
|
|
||||||
className="inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 px-6 py-2 text-sm font-medium text-white transition hover:border-white hover:bg-white hover:text-slate-900"
|
|
||||||
>
|
|
||||||
{ctaLabel}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isFlying ? (
|
|
||||||
<button type="button" onClick={handleBannerClick} className="bg-white/50 rounded-full absolute left-4 bottom-3 h-5 w-5 animate-bounce">
|
|
||||||
<ChevronsDown className="inset-0 h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Base content - always present under the banner */}
|
{/* Base content - always present under the banner */}
|
||||||
<div data-alt="home-banner-base" className={`relative p-6 px-12 flex items-center justify-center bg-[radial-gradient(ellipse_at_center,rgba(106,244,249,0.28)_0%,rgba(106,244,249,0.14)_35%,transparent_70%)] ${isDesktop ? 'min-h-[300px]' : 'min-h-[200px] !p-0'}`}>
|
<div className='p-1 text-center text-2xl font-bold tracking-tight sm:text-3xl'>Your idea. A movie. In minutes.</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'>Our AI turns sparks into full-blown stories — fast & free.</div>
|
||||||
|
<div data-alt="home-banner-base" className={`space-y-4 mx-auto w-full max-w-[900px] px-3 sm:px-16 relative py-2 px-12 flex items-center justify-center bg-[radial-gradient(ellipse_at_center,rgba(106,244,249,0.28)_0%,rgba(106,244,249,0.14)_35%,transparent_70%)] ${isDesktop ? 'px-24' : 'px-3'}`}>
|
||||||
<VideoCreationForm />
|
<VideoCreationForm />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,12 +3,8 @@
|
|||||||
import { useState, useRef, useEffect, useMemo } from 'react';
|
import { useState, useRef, useEffect, useMemo } from 'react';
|
||||||
import { PhotoPreviewSection } from '../PhotoPreview';
|
import { PhotoPreviewSection } from '../PhotoPreview';
|
||||||
import type { PhotoItem, PhotoType } from '../PhotoPreview/types';
|
import type { PhotoItem, PhotoType } from '../PhotoPreview/types';
|
||||||
|
import { ActionButton } from '@/components/common/ActionButton';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
|
||||||
UserOutlined,
|
|
||||||
CameraOutlined,
|
|
||||||
BulbOutlined,
|
|
||||||
ArrowRightOutlined,
|
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Dropdown, Popover } from 'antd';
|
import { Dropdown, Popover } from 'antd';
|
||||||
@ -17,7 +13,7 @@ import { MobileConfigModal } from './MobileConfigModal';
|
|||||||
import { AddItemModal } from './AddItemModal';
|
import { AddItemModal } from './AddItemModal';
|
||||||
import { defaultConfig } from './config-options';
|
import { defaultConfig } from './config-options';
|
||||||
import type { ConfigOptions } from './config-options';
|
import type { ConfigOptions } from './config-options';
|
||||||
import { CircleArrowRight, X } from 'lucide-react';
|
import { CircleArrowRight, X, Clapperboard } from 'lucide-react';
|
||||||
import TemplatePreviewModal from '@/components/common/TemplatePreviewModal';
|
import TemplatePreviewModal from '@/components/common/TemplatePreviewModal';
|
||||||
import { PcTemplateModal } from "@/components/ChatInputBox/PcTemplateModal"
|
import { PcTemplateModal } from "@/components/ChatInputBox/PcTemplateModal"
|
||||||
import { useDeviceType } from '@/hooks/useDeviceType';
|
import { useDeviceType } from '@/hooks/useDeviceType';
|
||||||
@ -48,7 +44,7 @@ export default function VideoCreationForm() {
|
|||||||
const [isRoleGenerating, setIsRoleGenerating] = useState<{ [key: string]: boolean }>({});
|
const [isRoleGenerating, setIsRoleGenerating] = useState<{ [key: string]: boolean }>({});
|
||||||
const [isItemGenerating, setIsItemGenerating] = useState<{ [key: string]: boolean }>({});
|
const [isItemGenerating, setIsItemGenerating] = useState<{ [key: string]: boolean }>({});
|
||||||
const [currentTemplate, setCurrentTemplate] = useState<StoryTemplateEntity | null>(null);
|
const [currentTemplate, setCurrentTemplate] = useState<StoryTemplateEntity | null>(null);
|
||||||
const [inputPlaceholder, setInputPlaceholder] = useState<string>('');
|
const [generateText, setGenerateText] = useState<string>('');
|
||||||
const [templateTitle, setTemplateTitle] = useState<string>('');
|
const [templateTitle, setTemplateTitle] = useState<string>('');
|
||||||
const [isMentionOpen, setIsMentionOpen] = useState(false);
|
const [isMentionOpen, setIsMentionOpen] = useState(false);
|
||||||
|
|
||||||
@ -95,7 +91,7 @@ export default function VideoCreationForm() {
|
|||||||
/** Clear current template related states */
|
/** Clear current template related states */
|
||||||
const clearTemplateSelection = () => {
|
const clearTemplateSelection = () => {
|
||||||
handleConfigChange('pcode', '');
|
handleConfigChange('pcode', '');
|
||||||
setInputPlaceholder('');
|
setGenerateText('');
|
||||||
setTemplateTitle('');
|
setTemplateTitle('');
|
||||||
setPhotos([]);
|
setPhotos([]);
|
||||||
};
|
};
|
||||||
@ -111,7 +107,7 @@ export default function VideoCreationForm() {
|
|||||||
})).filter(p => p.url);
|
})).filter(p => p.url);
|
||||||
|
|
||||||
setPhotos(prev => [...prev, ...characterPhotos]);
|
setPhotos(prev => [...prev, ...characterPhotos]);
|
||||||
setInputPlaceholder(template.generateText || template.name);
|
setGenerateText(template.generateText || template.name);
|
||||||
setTemplateTitle(template.name);
|
setTemplateTitle(template.name);
|
||||||
handleConfigChange('pcode', template.pcode || '');
|
handleConfigChange('pcode', template.pcode || '');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -396,9 +392,9 @@ export default function VideoCreationForm() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Template Description */}
|
{/* Template Description */}
|
||||||
{inputPlaceholder && (
|
{generateText && (
|
||||||
<div data-alt="template-description-wrapper" className="px-4 pt-1">
|
<div data-alt="template-description-wrapper" className="px-4 pt-1">
|
||||||
<div data-alt="template-description-text" className="text-white/25 text-sm">{inputPlaceholder}</div>
|
<div data-alt="template-description-text" className="text-white/25 text-sm">{generateText}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Text Input Area - Middle */}
|
{/* Text Input Area - Middle */}
|
||||||
@ -416,7 +412,7 @@ export default function VideoCreationForm() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 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="pl-4 pr-2 py-2 flex items-center justify-between gap-2">
|
||||||
{/* Left Side - Upload and Options */}
|
{/* Left Side - Upload and Options */}
|
||||||
<div data-alt="left-controls" className="flex items-center gap-2">
|
<div data-alt="left-controls" className="flex items-center gap-2">
|
||||||
{/* Upload Button with Dropdown */}
|
{/* Upload Button with Dropdown */}
|
||||||
@ -599,18 +595,13 @@ export default function VideoCreationForm() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Side - Create Button */}
|
{/* Right Side - Create Button */}
|
||||||
<button
|
<ActionButton
|
||||||
data-alt="create-button"
|
isCreating={isCreating}
|
||||||
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 disabled:opacity-50 disabled:cursor-not-allowed"
|
handleCreateVideo={handleCreate}
|
||||||
onClick={handleCreate}
|
icon={<Clapperboard className="text-base font-bold w-5 h-5" />}
|
||||||
disabled={isCreating}
|
width="w-10"
|
||||||
>
|
height="h-10"
|
||||||
{isCreating ? (
|
/>
|
||||||
<span className="animate-spin inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full" />
|
|
||||||
) : (
|
|
||||||
<ArrowRightOutlined className="text-base font-bold" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user