处理编译类型匹配问题

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 { 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 (
<div className="h-full bg-[#0C0E11] text-white">
<div className="h-full flex">
@ -209,7 +223,7 @@ export default function StoryboardView() {
{/* Filmstrip Preview */}
<div className="flex-shrink-0 py-6 px-8">
<SceneFilmstrip
scenes={scenes}
scenes={adaptScenesForFilmstrip(scenes)}
selectedSceneId={selectedSceneId}
onSceneSelect={(sceneId: string) => {
setSelectedSceneId(sceneId);

View File

@ -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<HTMLDivElement>(null);
const cardsRef = useRef<HTMLDivElement>(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<HTMLImageElement>;
const backgrounds = document.querySelectorAll('.card__bg') as NodeListOf<HTMLDivElement>;
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 = () => {
{/* 幽灵公主 */}
<Card className="princess-mononoke">
<CardBg className="card__bg" bg={monobg} />
<CardImg className="card__img" src={mono} alt="Princess Mononoke" />
<CardBg className="card__bg" bgUrl={monobg.src} />
<CardImg className="card__img" src={mono.src} alt="Princess Mononoke" />
<CardText>
<CardTitle>Princess Mononoke</CardTitle>
</CardText>
@ -79,8 +83,8 @@ const Parallax = () => {
{/* 千与千寻 */}
<Card className="spirited-away">
<CardBg className="card__bg" bg={spirited} />
<CardImg className="card__img" src={chihiro} alt="Spirited Away" />
<CardBg className="card__bg" bgUrl={spirited.src} />
<CardImg className="card__img" src={chihiro.src} alt="Spirited Away" />
<CardText>
<CardTitle>Spirited Away</CardTitle>
</CardText>
@ -88,8 +92,8 @@ const Parallax = () => {
{/* 哈尔的移动城堡 */}
<Card className="howl-s-moving-castle">
<CardBg className="card__bg" bg={howlbg} />
<CardImg className="card__img" src={howlcastle} alt="Howl's Moving Castle" />
<CardBg className="card__bg" bgUrl={howlbg.src} />
<CardImg className="card__img" src={howlcastle.src} alt="Howl's Moving Castle" />
<CardText>
<CardTitle>Howl's Moving Castle</CardTitle>
</CardText>
@ -176,7 +180,11 @@ const Card = styled.div`
}
`;
const CardBg = styled.div`
interface CardBgProps {
bgUrl: string;
}
const CardBg = styled.div<CardBgProps>`
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`

View File

@ -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<HTMLButtonElement> {
// 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 (
<motion.button
className={cn(
'relative rounded-full backdrop-blur-md transition-colors shadow-lg border',
variantStyles[variant],
sizeStyles[size],
className
)}
whileHover={{
scale: 1.05,
rotateX: 10,
translateZ: 10
}}
whileTap={{ scale: 0.95 }}
style={{
transformStyle: 'preserve-3d',
perspective: '1000px'
}}
{...props}
>
<Icon className={cn('text-white', iconSizes[size])} />
{tooltip && (
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs
bg-black/80 text-white rounded-md opacity-0 group-hover:opacity-100 transition-opacity
pointer-events-none whitespace-nowrap">
{tooltip}
</div>
)}
</motion.button>
);
}
// Create a motion button with forwardRef
const MotionButton = motion.button;
export const GlassIconButton = forwardRef<HTMLButtonElement, GlassIconButtonProps>(
({ icon: Icon, tooltip, variant = 'secondary', size = 'md', className, ...props }, ref) => {
return (
<MotionButton
ref={ref}
className={cn(
'relative rounded-full backdrop-blur-md transition-colors shadow-lg border',
variantStyles[variant],
sizeStyles[size],
className
)}
whileHover={{
scale: 1.05,
rotateX: 10,
translateZ: 10
}}
whileTap={{ scale: 0.95 }}
style={{
transformStyle: 'preserve-3d',
perspective: '1000px'
}}
{...props}
>
<Icon className={cn('text-white', iconSizes[size])} />
{tooltip && (
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs
bg-black/80 text-white rounded-md opacity-0 group-hover:opacity-100 transition-opacity
pointer-events-none whitespace-nowrap">
{tooltip}
</div>
)}
</MotionButton>
);
}
);

View File

@ -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<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
'relative h-4 w-full overflow-hidden rounded-full bg-secondary',
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;
interface ProgressProps {
value?: number;
max?: number;
className?: string;
}
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
({ className, value = 0, max = 100, ...props }, ref) => {
const percentage = Math.min(Math.max(0, value), max) / max * 100;
return (
<div
ref={ref}
className={cn(
'relative h-4 w-full overflow-hidden rounded-full bg-secondary',
className
)}
role="progressbar"
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 };

View File

@ -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)
}

85
package-lock.json generated
View File

@ -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",

View File

@ -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",