From faa4df49ec2cd7954f048b3ccc00c1fcb490a9b7 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Mon, 30 Jun 2025 12:29:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E7=BC=96=E8=AF=91=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=8C=B9=E9=85=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/pages/storyboard-view.tsx | 16 ++++- components/parallax.tsx | 54 ++++++++++------- components/ui/glass-icon-button.tsx | 84 ++++++++++++------------- components/ui/progress.tsx | 54 ++++++++++------- components/vanta-halo-background.tsx | 91 ++++++++++++++++++---------- package-lock.json | 85 ++++++++++++++++++++++---- package.json | 2 +- 7 files changed, 254 insertions(+), 132 deletions(-) diff --git a/components/pages/storyboard-view.tsx b/components/pages/storyboard-view.tsx index 9029a1f..eccdfd4 100644 --- a/components/pages/storyboard-view.tsx +++ b/components/pages/storyboard-view.tsx @@ -6,6 +6,7 @@ import { DragDropContext, DropResult } from 'react-beautiful-dnd'; import { ScriptMetaInfo } from '../script-overview/script-meta-info'; import { SceneFilmstrip } from '../script-overview/scene-filmstrip'; import { StoryboardCardList } from '../storyboard/storyboard-card-list'; +import { Scene } from '../pages/script-overview'; // 分镜场景数据结构 export interface StoryboardScene { @@ -183,6 +184,19 @@ export default function StoryboardView() { setScenes([...scenes, newScene]); }; + // 将 StoryboardScene 转换为 Scene 类型 + const adaptScenesForFilmstrip = (storyboardScenes: StoryboardScene[]): Scene[] => { + return storyboardScenes.map(scene => ({ + id: scene.id, + name: scene.name, + description: scene.description, + imageUrl: scene.imageUrl, + plot: scene.shot, // 使用 shot 作为 plot + dialogue: scene.frame, // 使用 frame 作为 dialogue + narration: scene.atmosphere // 使用 atmosphere 作为 narration + })); + }; + return (
@@ -209,7 +223,7 @@ export default function StoryboardView() { {/* Filmstrip Preview */}
{ setSelectedSceneId(sceneId); diff --git a/components/parallax.tsx b/components/parallax.tsx index 3234f3b..5c87c0e 100644 --- a/components/parallax.tsx +++ b/components/parallax.tsx @@ -10,23 +10,23 @@ import spirited from '@/assets/3dr_spirited.jpg'; import howlbg from '@/assets/3dr_howlbg.jpg'; const Parallax = () => { - const pageXRef = useRef(null); - const cardsRef = useRef(null); + const pageXRef = useRef(null); + const cardsRef = useRef(null); useEffect(() => { const pageX = pageXRef.current; const cards = cardsRef.current; - const images = document.querySelectorAll('.card__img'); - const backgrounds = document.querySelectorAll('.card__bg'); + const images = document.querySelectorAll('.card__img') as NodeListOf; + const backgrounds = document.querySelectorAll('.card__bg') as NodeListOf; - let timeout; + let timeout: number | undefined; const range = 40; // 计算旋转角度 - const calcValue = (a, b) => ((a / b) * range - range / 2).toFixed(1); + const calcValue = (a: number, b: number): string => ((a / b) * range - range / 2).toFixed(1); // 视差动画函数 - const parallax = (e) => { + const parallax = (e: MouseEvent): void => { const x = e.clientX; const y = e.clientY; @@ -39,7 +39,9 @@ const Parallax = () => { const yValue = calcValue(y, window.innerHeight); // 设置卡片容器的旋转角度 - cards.style.transform = `rotateX(${yValue}deg) rotateY(${xValue}deg)`; + if (cards) { + cards.style.transform = `rotateX(${yValue}deg) rotateY(${xValue}deg)`; + } // 设置所有图片的位移 images.forEach(item => { @@ -48,18 +50,20 @@ const Parallax = () => { // 设置所有背景的位置 backgrounds.forEach(item => { - item.style.backgroundPosition = `${xValue * 0.45}px ${-yValue * 0.45}px`; + item.style.backgroundPosition = `${Number(xValue) * 0.45}px ${-Number(yValue) * 0.45}px`; }); }); }; - pageX.addEventListener('mousemove', parallax, false); + if (pageX) { + pageX.addEventListener('mousemove', parallax as EventListener, false); + + return () => { + pageX.removeEventListener('mousemove', parallax as EventListener); + }; + } - return () => { - if (pageX) { - pageX.removeEventListener('mousemove', parallax); - } - }; + return undefined; }, []); return ( @@ -70,8 +74,8 @@ const Parallax = () => { {/* 幽灵公主 */} - - + + Princess Mononoke @@ -79,8 +83,8 @@ const Parallax = () => { {/* 千与千寻 */} - - + + Spirited Away @@ -88,8 +92,8 @@ const Parallax = () => { {/* 哈尔的移动城堡 */} - - + + Howl's Moving Castle @@ -176,7 +180,11 @@ const Card = styled.div` } `; -const CardBg = styled.div` +interface CardBgProps { + bgUrl: string; +} + +const CardBg = styled.div` bottom: -50px; left: -50px; position: absolute; @@ -185,7 +193,7 @@ const CardBg = styled.div` transform-origin: 50% 50%; transform: translateZ(-50px); z-index: 0; - background: ${props => `url(${props.bg}) center/cover no-repeat`}; + background: ${props => `url(${props.bgUrl}) center/cover no-repeat`}; `; const CardImg = styled.img` diff --git a/components/ui/glass-icon-button.tsx b/components/ui/glass-icon-button.tsx index 3faeea4..a7742d8 100644 --- a/components/ui/glass-icon-button.tsx +++ b/components/ui/glass-icon-button.tsx @@ -1,15 +1,18 @@ 'use client'; -import React from 'react'; +import React, { forwardRef } from 'react'; import { motion } from 'framer-motion'; import { LucideIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; -interface GlassIconButtonProps extends React.ButtonHTMLAttributes { +// Define props without the ref +interface GlassIconButtonProps { icon: LucideIcon; tooltip?: string; variant?: 'primary' | 'secondary' | 'danger'; size?: 'sm' | 'md' | 'lg'; + className?: string; + [key: string]: any; // To allow spreading other props } const variantStyles = { @@ -30,42 +33,41 @@ const iconSizes = { lg: 'w-6 h-6', }; -export function GlassIconButton({ - icon: Icon, - tooltip, - variant = 'secondary', - size = 'md', - className, - ...props -}: GlassIconButtonProps) { - return ( - - - {tooltip && ( -
- {tooltip} -
- )} -
- ); -} \ No newline at end of file +// Create a motion button with forwardRef +const MotionButton = motion.button; + +export const GlassIconButton = forwardRef( + ({ icon: Icon, tooltip, variant = 'secondary', size = 'md', className, ...props }, ref) => { + return ( + + + {tooltip && ( +
+ {tooltip} +
+ )} +
+ ); + } +); \ No newline at end of file diff --git a/components/ui/progress.tsx b/components/ui/progress.tsx index b959918..6b51c44 100644 --- a/components/ui/progress.tsx +++ b/components/ui/progress.tsx @@ -1,28 +1,40 @@ 'use client'; import * as React from 'react'; -import * as ProgressPrimitive from '@radix-ui/react-progress'; - import { cn } from '@/lib/utils'; -const Progress = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, value, ...props }, ref) => ( - - - -)); -Progress.displayName = ProgressPrimitive.Root.displayName; +interface ProgressProps { + value?: number; + max?: number; + className?: string; +} + +const Progress = React.forwardRef( + ({ className, value = 0, max = 100, ...props }, ref) => { + const percentage = Math.min(Math.max(0, value), max) / max * 100; + + return ( +
+
+
+ ); + } +); + +Progress.displayName = "Progress"; export { Progress }; diff --git a/components/vanta-halo-background.tsx b/components/vanta-halo-background.tsx index ffddbbc..db00e87 100644 --- a/components/vanta-halo-background.tsx +++ b/components/vanta-halo-background.tsx @@ -9,13 +9,26 @@ interface VantaHaloBackgroundProps { onLoaded?: () => void; } +// Add VANTA to the window type +declare global { + interface Window { + VANTA: any; + } +} + // 预加载 Vanta 脚本 const preloadVantaScript = () => { const link = document.createElement('link') link.rel = 'preload' link.as = 'script' - link.href = '/lib/vanta.halo.min.js' // 确保路径正确 + link.href = '/js/three.min.js' document.head.appendChild(link) + + const link2 = document.createElement('link') + link2.rel = 'preload' + link2.as = 'script' + link2.href = '/lib/vanta.halo.min.js' // 确保路径正确 + document.head.appendChild(link2) } // 使用 React.memo 来避免不必要的重渲染 @@ -30,41 +43,55 @@ const VantaHaloBackground = memo(({ onLoaded }: VantaHaloBackgroundProps) => { const loadVanta = async () => { try { - const VANTA = await import('../lib/vanta.halo.min.js') + // Dynamically load the script instead of importing + const threeScript = document.createElement('script') + threeScript.src = '/js/three.min.js' + document.body.appendChild(threeScript) - if (canceled || !vantaRef.current || effectInstance.current) return + threeScript.onload = () => { + const vantaScript = document.createElement('script') + vantaScript.src = '/lib/vanta.halo.min.js' + document.body.appendChild(vantaScript) - // 使用 requestAnimationFrame 来控制动画帧率 - const animate = () => { - if (effectInstance.current) { - effectInstance.current.frameRequestId = requestAnimationFrame(animate) + vantaScript.onload = () => { + if (canceled || !vantaRef.current || effectInstance.current) return + + // 使用 requestAnimationFrame 来控制动画帧率 + const animate = () => { + if (effectInstance.current) { + effectInstance.current.frameRequestId = requestAnimationFrame(animate) + } + } + + // Access VANTA from the window object after script loads + if (window.VANTA && window.VANTA.HALO) { + effectInstance.current = window.VANTA.HALO({ + el: vantaRef.current, + THREE, + mouseControls: true, + touchControls: true, + gyroControls: false, + scale: 1.0, + scaleMobile: 1.0, + amplitudeFactor: 1.5, + ringFactor: 1.3, + size: 1.2, + minHeight: 200.00, + minWidth: 200.00, + // 优化渲染性能的参数 + fps: 30, // 限制帧率 + renderCacheSize: 4, // 缓存大小 + }) + + frameId.current = requestAnimationFrame(animate) + + // 通知加载完成 + if (onLoaded) { + onLoaded(); + } + } } } - - effectInstance.current = VANTA.default({ - el: vantaRef.current, - THREE, - mouseControls: true, - touchControls: true, - gyroControls: false, - scale: 1.0, - scaleMobile: 1.0, - amplitudeFactor: 1.5, - ringFactor: 1.3, - size: 1.2, - minHeight: 200.00, - minWidth: 200.00, - // 优化渲染性能的参数 - fps: 30, // 限制帧率 - renderCacheSize: 4, // 缓存大小 - }) - - frameId.current = requestAnimationFrame(animate) - - // 通知加载完成 - if (onLoaded) { - onLoaded(); - } } catch (error) { console.error('Failed to load Vanta effect:', error) } diff --git a/package-lock.json b/package-lock.json index 463a08d..934bbd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-popover": "^1.1.1", - "@radix-ui/react-progress": "^1.1.0", + "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.2.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.2.5", @@ -1457,12 +1457,13 @@ } }, "node_modules/@radix-ui/react-progress": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz", - "integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", "dependencies": { - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0" + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", @@ -1479,10 +1480,67 @@ } } }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -3705,9 +3763,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001667", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", - "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -3721,7 +3779,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", diff --git a/package.json b/package.json index 3d94999..7e51e8a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-popover": "^1.1.1", - "@radix-ui/react-progress": "^1.1.0", + "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.2.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.2.5",