处理编译类型匹配问题

This commit is contained in:
Xin Wang 2025-06-30 12:29:35 +08:00
parent 1e0b6bdb49
commit faa4df49ec
7 changed files with 254 additions and 132 deletions

View File

@ -6,6 +6,7 @@ import { DragDropContext, DropResult } from 'react-beautiful-dnd';
import { ScriptMetaInfo } from '../script-overview/script-meta-info'; import { ScriptMetaInfo } from '../script-overview/script-meta-info';
import { SceneFilmstrip } from '../script-overview/scene-filmstrip'; import { SceneFilmstrip } from '../script-overview/scene-filmstrip';
import { StoryboardCardList } from '../storyboard/storyboard-card-list'; import { StoryboardCardList } from '../storyboard/storyboard-card-list';
import { Scene } from '../pages/script-overview';
// 分镜场景数据结构 // 分镜场景数据结构
export interface StoryboardScene { export interface StoryboardScene {
@ -183,6 +184,19 @@ export default function StoryboardView() {
setScenes([...scenes, newScene]); 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 ( return (
<div className="h-full bg-[#0C0E11] text-white"> <div className="h-full bg-[#0C0E11] text-white">
<div className="h-full flex"> <div className="h-full flex">
@ -209,7 +223,7 @@ export default function StoryboardView() {
{/* Filmstrip Preview */} {/* Filmstrip Preview */}
<div className="flex-shrink-0 py-6 px-8"> <div className="flex-shrink-0 py-6 px-8">
<SceneFilmstrip <SceneFilmstrip
scenes={scenes} scenes={adaptScenesForFilmstrip(scenes)}
selectedSceneId={selectedSceneId} selectedSceneId={selectedSceneId}
onSceneSelect={(sceneId: string) => { onSceneSelect={(sceneId: string) => {
setSelectedSceneId(sceneId); setSelectedSceneId(sceneId);

View File

@ -10,23 +10,23 @@ import spirited from '@/assets/3dr_spirited.jpg';
import howlbg from '@/assets/3dr_howlbg.jpg'; import howlbg from '@/assets/3dr_howlbg.jpg';
const Parallax = () => { const Parallax = () => {
const pageXRef = useRef(null); const pageXRef = useRef<HTMLDivElement>(null);
const cardsRef = useRef(null); const cardsRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
const pageX = pageXRef.current; const pageX = pageXRef.current;
const cards = cardsRef.current; const cards = cardsRef.current;
const images = document.querySelectorAll('.card__img'); const images = document.querySelectorAll('.card__img') as NodeListOf<HTMLImageElement>;
const backgrounds = document.querySelectorAll('.card__bg'); const backgrounds = document.querySelectorAll('.card__bg') as NodeListOf<HTMLDivElement>;
let timeout; let timeout: number | undefined;
const range = 40; 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 x = e.clientX;
const y = e.clientY; const y = e.clientY;
@ -39,7 +39,9 @@ const Parallax = () => {
const yValue = calcValue(y, window.innerHeight); 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 => { images.forEach(item => {
@ -48,18 +50,20 @@ const Parallax = () => {
// 设置所有背景的位置 // 设置所有背景的位置
backgrounds.forEach(item => { 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 () => { return undefined;
if (pageX) {
pageX.removeEventListener('mousemove', parallax);
}
};
}, []); }, []);
return ( return (
@ -70,8 +74,8 @@ const Parallax = () => {
{/* 幽灵公主 */} {/* 幽灵公主 */}
<Card className="princess-mononoke"> <Card className="princess-mononoke">
<CardBg className="card__bg" bg={monobg} /> <CardBg className="card__bg" bgUrl={monobg.src} />
<CardImg className="card__img" src={mono} alt="Princess Mononoke" /> <CardImg className="card__img" src={mono.src} alt="Princess Mononoke" />
<CardText> <CardText>
<CardTitle>Princess Mononoke</CardTitle> <CardTitle>Princess Mononoke</CardTitle>
</CardText> </CardText>
@ -79,8 +83,8 @@ const Parallax = () => {
{/* 千与千寻 */} {/* 千与千寻 */}
<Card className="spirited-away"> <Card className="spirited-away">
<CardBg className="card__bg" bg={spirited} /> <CardBg className="card__bg" bgUrl={spirited.src} />
<CardImg className="card__img" src={chihiro} alt="Spirited Away" /> <CardImg className="card__img" src={chihiro.src} alt="Spirited Away" />
<CardText> <CardText>
<CardTitle>Spirited Away</CardTitle> <CardTitle>Spirited Away</CardTitle>
</CardText> </CardText>
@ -88,8 +92,8 @@ const Parallax = () => {
{/* 哈尔的移动城堡 */} {/* 哈尔的移动城堡 */}
<Card className="howl-s-moving-castle"> <Card className="howl-s-moving-castle">
<CardBg className="card__bg" bg={howlbg} /> <CardBg className="card__bg" bgUrl={howlbg.src} />
<CardImg className="card__img" src={howlcastle} alt="Howl's Moving Castle" /> <CardImg className="card__img" src={howlcastle.src} alt="Howl's Moving Castle" />
<CardText> <CardText>
<CardTitle>Howl's Moving Castle</CardTitle> <CardTitle>Howl's Moving Castle</CardTitle>
</CardText> </CardText>
@ -176,7 +180,11 @@ const Card = styled.div`
} }
`; `;
const CardBg = styled.div` interface CardBgProps {
bgUrl: string;
}
const CardBg = styled.div<CardBgProps>`
bottom: -50px; bottom: -50px;
left: -50px; left: -50px;
position: absolute; position: absolute;
@ -185,7 +193,7 @@ const CardBg = styled.div`
transform-origin: 50% 50%; transform-origin: 50% 50%;
transform: translateZ(-50px); transform: translateZ(-50px);
z-index: 0; 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` const CardImg = styled.img`

View File

@ -1,15 +1,18 @@
'use client'; 'use client';
import React from 'react'; import React, { forwardRef } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { LucideIcon } from 'lucide-react'; import { LucideIcon } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
interface GlassIconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { // Define props without the ref
interface GlassIconButtonProps {
icon: LucideIcon; icon: LucideIcon;
tooltip?: string; tooltip?: string;
variant?: 'primary' | 'secondary' | 'danger'; variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg'; size?: 'sm' | 'md' | 'lg';
className?: string;
[key: string]: any; // To allow spreading other props
} }
const variantStyles = { const variantStyles = {
@ -30,42 +33,41 @@ const iconSizes = {
lg: 'w-6 h-6', lg: 'w-6 h-6',
}; };
export function GlassIconButton({ // Create a motion button with forwardRef
icon: Icon, const MotionButton = motion.button;
tooltip,
variant = 'secondary', export const GlassIconButton = forwardRef<HTMLButtonElement, GlassIconButtonProps>(
size = 'md', ({ icon: Icon, tooltip, variant = 'secondary', size = 'md', className, ...props }, ref) => {
className, return (
...props <MotionButton
}: GlassIconButtonProps) { ref={ref}
return ( className={cn(
<motion.button 'relative rounded-full backdrop-blur-md transition-colors shadow-lg border',
className={cn( variantStyles[variant],
'relative rounded-full backdrop-blur-md transition-colors shadow-lg border', sizeStyles[size],
variantStyles[variant], className
sizeStyles[size], )}
className whileHover={{
)} scale: 1.05,
whileHover={{ rotateX: 10,
scale: 1.05, translateZ: 10
rotateX: 10, }}
translateZ: 10 whileTap={{ scale: 0.95 }}
}} style={{
whileTap={{ scale: 0.95 }} transformStyle: 'preserve-3d',
style={{ perspective: '1000px'
transformStyle: 'preserve-3d', }}
perspective: '1000px' {...props}
}} >
{...props} <Icon className={cn('text-white', iconSizes[size])} />
> {tooltip && (
<Icon className={cn('text-white', iconSizes[size])} /> <div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs
{tooltip && ( bg-black/80 text-white rounded-md opacity-0 group-hover:opacity-100 transition-opacity
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs pointer-events-none whitespace-nowrap">
bg-black/80 text-white rounded-md opacity-0 group-hover:opacity-100 transition-opacity {tooltip}
pointer-events-none whitespace-nowrap"> </div>
{tooltip} )}
</div> </MotionButton>
)} );
</motion.button> }
); );
}

View File

@ -1,28 +1,40 @@
'use client'; 'use client';
import * as React from 'react'; import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const Progress = React.forwardRef< interface ProgressProps {
React.ElementRef<typeof ProgressPrimitive.Root>, value?: number;
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> max?: number;
>(({ className, value, ...props }, ref) => ( className?: string;
<ProgressPrimitive.Root }
ref={ref}
className={cn( const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
'relative h-4 w-full overflow-hidden rounded-full bg-secondary', ({ className, value = 0, max = 100, ...props }, ref) => {
className const percentage = Math.min(Math.max(0, value), max) / max * 100;
)}
{...props} return (
> <div
<ProgressPrimitive.Indicator ref={ref}
className="h-full w-full flex-1 bg-primary transition-all" className={cn(
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} 'relative h-4 w-full overflow-hidden rounded-full bg-secondary',
/> className
</ProgressPrimitive.Root> )}
)); role="progressbar"
Progress.displayName = ProgressPrimitive.Root.displayName; aria-valuemin={0}
aria-valuemax={max}
aria-valuenow={value}
{...props}
>
<div
className="h-full flex-1 bg-primary transition-all"
style={{ width: `${percentage}%` }}
/>
</div>
);
}
);
Progress.displayName = "Progress";
export { Progress }; export { Progress };

View File

@ -9,13 +9,26 @@ interface VantaHaloBackgroundProps {
onLoaded?: () => void; onLoaded?: () => void;
} }
// Add VANTA to the window type
declare global {
interface Window {
VANTA: any;
}
}
// 预加载 Vanta 脚本 // 预加载 Vanta 脚本
const preloadVantaScript = () => { const preloadVantaScript = () => {
const link = document.createElement('link') const link = document.createElement('link')
link.rel = 'preload' link.rel = 'preload'
link.as = 'script' link.as = 'script'
link.href = '/lib/vanta.halo.min.js' // 确保路径正确 link.href = '/js/three.min.js'
document.head.appendChild(link) 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 来避免不必要的重渲染 // 使用 React.memo 来避免不必要的重渲染
@ -30,41 +43,55 @@ const VantaHaloBackground = memo(({ onLoaded }: VantaHaloBackgroundProps) => {
const loadVanta = async () => { const loadVanta = async () => {
try { 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 来控制动画帧率 vantaScript.onload = () => {
const animate = () => { if (canceled || !vantaRef.current || effectInstance.current) return
if (effectInstance.current) {
effectInstance.current.frameRequestId = requestAnimationFrame(animate) // 使用 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) { } catch (error) {
console.error('Failed to load Vanta effect:', error) console.error('Failed to load Vanta effect:', error)
} }

85
package-lock.json generated
View File

@ -27,7 +27,7 @@
"@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1", "@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-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.5",
@ -1457,12 +1457,13 @@
} }
}, },
"node_modules/@radix-ui/react-progress": { "node_modules/@radix-ui/react-progress": {
"version": "1.1.0", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
"integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==", "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/react-context": "1.1.0", "@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.0.0" "@radix-ui/react-primitive": "2.1.3"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": "*", "@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": { "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
"version": "1.1.0", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
"integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", "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": { "peerDependencies": {
"@types/react": "*", "@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@ -3705,9 +3763,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001667", "version": "1.0.30001726",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz",
"integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -3721,7 +3779,8 @@
"type": "github", "type": "github",
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
] ],
"license": "CC-BY-4.0"
}, },
"node_modules/chalk": { "node_modules/chalk": {
"version": "4.1.2", "version": "4.1.2",

View File

@ -28,7 +28,7 @@
"@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1", "@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-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.5",