forked from 77media/video-flow
loading动画以及错误展示
This commit is contained in:
parent
d16970c45b
commit
568cb65cf3
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import './globals.css';
|
||||
import '@/iconfont/iconfont.css';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { Providers } from '@/components/providers';
|
||||
import { ConfigProvider, theme } from 'antd';
|
||||
|
||||
@ -14,6 +14,7 @@ import { Drawer } from 'antd';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import error_image from '@/public/assets/error.webp';
|
||||
import { showDownloadOptionsModal } from './download-options-modal';
|
||||
import RenderLoading from './RenderLoading';
|
||||
|
||||
interface H5MediaViewerProps {
|
||||
/** 任务对象,包含各阶段数据 */
|
||||
@ -150,9 +151,10 @@ export function H5MediaViewer({
|
||||
return [...roles, ...scenes].map(item => ({
|
||||
url: item?.url as string | undefined,
|
||||
status: item?.status as number | undefined,
|
||||
type: item?.type as string | undefined,
|
||||
}));
|
||||
}
|
||||
return [] as Array<{ url?: string; status?: number }>;
|
||||
return [] as Array<{ url?: string; status?: number; type?: string }>;
|
||||
}, [stage, taskObject.roles?.data, taskObject.scenes?.data]);
|
||||
|
||||
// 占位,避免未使用警告
|
||||
@ -266,18 +268,19 @@ export function H5MediaViewer({
|
||||
<div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="video-status" style={{
|
||||
height: videoWrapperHeight,
|
||||
}}>
|
||||
{status === 0 && (
|
||||
<span className="text-blue-500 text-base">Generating...</span>
|
||||
{(status === 0 || status === 2) && (
|
||||
<RenderLoading
|
||||
recordSize="120px"
|
||||
loadingText={status === 0 ? 'Generating video...' : 'Generate failed'}
|
||||
isFailed={status === 2}
|
||||
/>
|
||||
)}
|
||||
{status === 2 && (
|
||||
{/* {status === 2 && (
|
||||
<div className="flex flex-col items-center justify-center gap-1">
|
||||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||||
<span className="text-[#fa485f] text-base">Generate failed</span>
|
||||
</div>
|
||||
)}
|
||||
{status !== 0 && status !== 2 && (
|
||||
<span className="text-white/70 text-base">Pending</span>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -315,15 +318,19 @@ export function H5MediaViewer({
|
||||
<div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="image-status" style={{
|
||||
height: imageWrapperHeight,
|
||||
}}>
|
||||
{status === 0 && (
|
||||
<span className="text-blue-500 text-base">Generating...</span>
|
||||
{(status === 0 || status === 2) && (
|
||||
<RenderLoading
|
||||
recordSize="120px"
|
||||
loadingText={status === 0 ? (item.type === 'role' ? 'Generating role...' : 'Generating scene...') : 'Generate failed'}
|
||||
isFailed={status === 2}
|
||||
/>
|
||||
)}
|
||||
{status === 2 && (
|
||||
{/* {status === 2 && (
|
||||
<div className="flex flex-col items-center justify-center gap-1">
|
||||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||||
<span className="text-[#fa485f] text-base">Generate failed</span>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -544,8 +551,9 @@ export function H5MediaViewer({
|
||||
</div>
|
||||
)}
|
||||
<style jsx global>{`
|
||||
[data-alt='carousel-wrapper'] .slick-slide { display: flex !important;justify-content: center; }
|
||||
[data-alt='carousel-wrapper'] .slick-slide { width: 100%;display: flex !important;justify-content: center; }
|
||||
.slick-slider { height: 100% !important; }
|
||||
.slick-current > div { width: 100% !important; }
|
||||
.ant-carousel { height: 100% !important; }
|
||||
.slick-list { width: 100%;height: 100% !important;max-height: 100%; }
|
||||
.slick-track { display: flex !important; align-items: center;height: 100% !important; }
|
||||
|
||||
448
components/pages/work-flow/RenderLoading.tsx
Normal file
448
components/pages/work-flow/RenderLoading.tsx
Normal file
@ -0,0 +1,448 @@
|
||||
import React from 'react';
|
||||
|
||||
/** Props for RenderLoading component */
|
||||
interface RenderLoadingProps {
|
||||
/** Size of the vinyl record */
|
||||
recordSize?: string;
|
||||
/** Apple silver light color for metallic effect */
|
||||
appleSilverLight?: string;
|
||||
/** Apple silver main color */
|
||||
appleSilverMain?: string;
|
||||
/** Apple silver dark color for shadows */
|
||||
appleSilverDark?: string;
|
||||
/** Core gradient start color (cyan) */
|
||||
coreGradientStart?: string;
|
||||
/** Core gradient middle color (purple) */
|
||||
coreGradientMid?: string;
|
||||
/** Core gradient end color (magenta) */
|
||||
coreGradientEnd?: string;
|
||||
/** Core glow color effect */
|
||||
coreGlowColor?: string;
|
||||
/** Scanner light color */
|
||||
scannerColor?: string;
|
||||
/** Record background color */
|
||||
recordBg?: string;
|
||||
/** Overall background color */
|
||||
backgroundColor?: string;
|
||||
/** Loading text */
|
||||
loadingText?: string;
|
||||
/** Failed state trigger */
|
||||
isFailed?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vinyl record loading animation component with pulsing core and tonearm
|
||||
* @param props - Component configuration props
|
||||
* @returns React component with animated vinyl record loader
|
||||
*/
|
||||
const RenderLoading: React.FC<RenderLoadingProps> = ({
|
||||
recordSize = '250px',
|
||||
appleSilverLight = '#E5E5EA',
|
||||
appleSilverMain = '#D1D1D6',
|
||||
appleSilverDark = '#8A8A8E',
|
||||
coreGradientStart = '#00E4E4',
|
||||
coreGradientMid = '#8A2BE2',
|
||||
coreGradientEnd = '#FF00FF',
|
||||
coreGlowColor = 'rgba(138, 43, 226, 0.9)',
|
||||
scannerColor = 'rgba(0, 228, 228, 0.35)',
|
||||
recordBg = '#1A1A1A',
|
||||
backgroundColor = '#000',
|
||||
loadingText = 'Generating...',
|
||||
isFailed = false,
|
||||
}) => {
|
||||
/** Dead metal color for failed state */
|
||||
const deadMetalColor = '#3A3A3A';
|
||||
const styles = `
|
||||
@keyframes rotateRecord {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes rotateScanner {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes placeTonearm {
|
||||
from { transform: rotate(-30deg); }
|
||||
to { transform: rotate(0deg); }
|
||||
}
|
||||
|
||||
@keyframes bobTonearm {
|
||||
0%, 100% {
|
||||
transform: rotate(0deg) translateY(0);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(0.5deg) translateY(2px);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(-0.5deg) translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes retractTonearm {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(-30deg); }
|
||||
}
|
||||
|
||||
@keyframes breakTonearm {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
animation-timing-function: cubic-bezier(0.3, 0, 0.8, 0.7); /* 加速下坠 */
|
||||
}
|
||||
60% {
|
||||
transform: rotate(-55deg); /* 过冲到最大角度 */
|
||||
animation-timing-function: cubic-bezier(0.1, 0.3, 0.4, 1.5); /* 回弹 */
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-90deg); /* 稳定在最终角度 */
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 15px 6px ${coreGlowColor},
|
||||
inset 0 0 4px 1px rgba(255,255,255,0.4);
|
||||
opacity: 0.9;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.4);
|
||||
box-shadow: 0 0 35px 15px ${coreGlowColor},
|
||||
inset 0 0 8px 3px rgba(255,255,255,0.7);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{styles}</style>
|
||||
<div
|
||||
data-alt="vinyl-loading-container"
|
||||
style={{
|
||||
margin: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor,
|
||||
fontFamily: 'sans-serif',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
data-alt="loading-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: recordSize,
|
||||
height: recordSize,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{/* Vinyl Record Container */}
|
||||
<div
|
||||
data-alt="vinyl-record"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
animation: isFailed ? 'none' : 'rotateRecord 4s linear infinite',
|
||||
transformStyle: 'preserve-3d',
|
||||
}}
|
||||
>
|
||||
{/* Record Half - Top */}
|
||||
<div
|
||||
data-alt="record-half-top"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: recordBg,
|
||||
borderRadius: '50%',
|
||||
boxShadow: `0 15px 35px rgba(0, 0, 0, 0.7),
|
||||
inset 0 0 20px rgba(0,0,0,1),
|
||||
inset 0 0 2px 1px rgba(255, 255, 255, 0.1)`,
|
||||
overflow: 'hidden',
|
||||
clipPath: isFailed
|
||||
? 'polygon(0 0, 100% 0, 100% 50%, 85% 51%, 70% 49%, 55% 52%, 40% 50%, 25% 51.5%, 10% 49.5%, 0 50%)'
|
||||
: 'polygon(0 0, 100% 0, 100% 50%, 0 50%)',
|
||||
transition: 'transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55), clip-path 0.3s ease',
|
||||
transform: isFailed ? 'translateY(-10px) rotate(-4deg)' : 'none',
|
||||
}}
|
||||
>
|
||||
{/* Record grooves effect */}
|
||||
<div
|
||||
data-alt="record-grooves-top"
|
||||
style={{
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
backgroundImage: `repeating-radial-gradient(
|
||||
circle at center,
|
||||
rgba(255, 255, 255, 0.1) 0,
|
||||
rgba(255, 255, 255, 0.15) 1px,
|
||||
transparent 1px,
|
||||
transparent 7px
|
||||
)`,
|
||||
mixBlendMode: 'overlay',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Scanner light */}
|
||||
<div
|
||||
data-alt="scanner-light-top"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
background: `conic-gradient(
|
||||
from 90deg,
|
||||
transparent 0%,
|
||||
${scannerColor} 10%,
|
||||
transparent 25%
|
||||
)`,
|
||||
animation: isFailed ? 'none' : 'rotateScanner 3s linear infinite',
|
||||
zIndex: 2,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Center label */}
|
||||
<div
|
||||
data-alt="center-label-top"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto',
|
||||
width: '38%',
|
||||
height: '38%',
|
||||
background: '#111',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
boxShadow: 'inset 0 0 20px rgba(0, 0, 0, 1)',
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
{/* Pulsing core */}
|
||||
<div
|
||||
data-alt="pulsing-core-top"
|
||||
style={{
|
||||
width: '12%',
|
||||
height: '12%',
|
||||
background: isFailed
|
||||
? appleSilverDark
|
||||
: `radial-gradient(circle at center, ${coreGradientStart} 0%, ${coreGradientMid} 50%, ${coreGradientEnd} 100%)`,
|
||||
borderRadius: '50%',
|
||||
animation: isFailed ? 'none' : 'pulse 1.5s ease-in-out infinite',
|
||||
boxShadow: isFailed ? 'none' : undefined,
|
||||
transition: 'background 0.3s, box-shadow 0.3s',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Record Half - Bottom */}
|
||||
<div
|
||||
data-alt="record-half-bottom"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: recordBg,
|
||||
borderRadius: '50%',
|
||||
boxShadow: `0 15px 35px rgba(0, 0, 0, 0.7),
|
||||
inset 0 0 20px rgba(0,0,0,1),
|
||||
inset 0 0 2px 1px rgba(255, 255, 255, 0.1)`,
|
||||
overflow: 'hidden',
|
||||
clipPath: isFailed
|
||||
? 'polygon(0 50%, 10% 49.5%, 25% 51.5%, 40% 50%, 55% 52%, 70% 49%, 85% 51%, 100% 50%, 100% 100%, 0 100%)'
|
||||
: 'polygon(0 50%, 100% 50%, 100% 100%, 0 100%)',
|
||||
transition: 'transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55), clip-path 0.3s ease',
|
||||
transform: isFailed ? 'translateY(10px) rotate(4deg)' : 'none',
|
||||
}}
|
||||
>
|
||||
{/* Record grooves effect */}
|
||||
<div
|
||||
data-alt="record-grooves-bottom"
|
||||
style={{
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
backgroundImage: `repeating-radial-gradient(
|
||||
circle at center,
|
||||
rgba(255, 255, 255, 0.1) 0,
|
||||
rgba(255, 255, 255, 0.15) 1px,
|
||||
transparent 1px,
|
||||
transparent 7px
|
||||
)`,
|
||||
mixBlendMode: 'overlay',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Scanner light */}
|
||||
<div
|
||||
data-alt="scanner-light-bottom"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
background: `conic-gradient(
|
||||
from 90deg,
|
||||
transparent 0%,
|
||||
${scannerColor} 10%,
|
||||
transparent 25%
|
||||
)`,
|
||||
animation: isFailed ? 'none' : 'rotateScanner 3s linear infinite',
|
||||
zIndex: 2,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Center label */}
|
||||
<div
|
||||
data-alt="center-label-bottom"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto',
|
||||
width: '38%',
|
||||
height: '38%',
|
||||
background: '#111',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
boxShadow: 'inset 0 0 20px rgba(0, 0, 0, 1)',
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
{/* Pulsing core */}
|
||||
<div
|
||||
data-alt="pulsing-core-bottom"
|
||||
style={{
|
||||
width: '12%',
|
||||
height: '12%',
|
||||
background: isFailed
|
||||
? appleSilverDark
|
||||
: `radial-gradient(circle at center, ${coreGradientStart} 0%, ${coreGradientMid} 50%, ${coreGradientEnd} 100%)`,
|
||||
borderRadius: '50%',
|
||||
animation: isFailed ? 'none' : 'pulse 1.5s ease-in-out infinite',
|
||||
boxShadow: isFailed ? 'none' : undefined,
|
||||
transition: 'background 0.3s, box-shadow 0.3s',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tonearm */}
|
||||
<div
|
||||
data-alt="tonearm"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: `calc(${recordSize} * 0.5)`,
|
||||
height: `calc(${recordSize} * 0.1)`,
|
||||
right: `calc(${recordSize} * -0.25)`,
|
||||
top: '35%',
|
||||
transformOrigin: `calc(100% - ${recordSize} * 0.06) center`,
|
||||
zIndex: 10,
|
||||
animation: isFailed
|
||||
? 'breakTonearm 1.2s forwards'
|
||||
: 'placeTonearm 1.5s ease-in-out forwards, bobTonearm 1s ease-in-out 1.5s infinite',
|
||||
}}
|
||||
>
|
||||
{/* Arm */}
|
||||
<div
|
||||
data-alt="tonearm-arm"
|
||||
style={{
|
||||
width: '88%',
|
||||
height: '30%',
|
||||
background: isFailed
|
||||
? deadMetalColor
|
||||
: `linear-gradient(90deg,
|
||||
${appleSilverDark} 0%,
|
||||
${appleSilverLight} 50%,
|
||||
${appleSilverDark} 100%)`,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
borderRadius: '10px',
|
||||
transition: 'background 0.3s ease',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Head */}
|
||||
<div
|
||||
data-alt="tonearm-head"
|
||||
style={{
|
||||
width: '20%',
|
||||
height: '60%',
|
||||
background: isFailed ? deadMetalColor : appleSilverMain,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
borderRadius: '4px',
|
||||
boxShadow: 'inset 0 0 4px rgba(0,0,0,0.4)',
|
||||
transition: 'background 0.3s ease',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Pivot */}
|
||||
<div
|
||||
data-alt="tonearm-pivot"
|
||||
style={{
|
||||
width: `calc(${recordSize} * 0.12)`,
|
||||
height: `calc(${recordSize} * 0.12)`,
|
||||
background: isFailed
|
||||
? deadMetalColor
|
||||
: `radial-gradient(circle at 65% 35%, ${appleSilverLight}, ${appleSilverMain} 60%, ${appleSilverDark} 100%)`,
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
boxShadow: isFailed
|
||||
? '2px 2px 5px rgba(0,0,0,0.4), inset 1px 1px 1px #222'
|
||||
: `2px 2px 5px rgba(0,0,0,0.4), inset 1px 1px 1px ${appleSilverLight}`,
|
||||
transition: 'background 0.3s ease, box-shadow 0.3s ease',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* 加载/失败文案 */}
|
||||
<div data-alt="loading-text" className={`text-center ${isFailed ? 'text-[#d2c6c6]' : 'text-white'}`}>
|
||||
{loadingText}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenderLoading;
|
||||
|
||||
@ -18,8 +18,7 @@ import { VideoEditOverlay } from './video-edit/VideoEditOverlay';
|
||||
import { EditPoint as EditPointType } from './video-edit/types';
|
||||
import { isVideoModificationEnabled } from '@/lib/server-config';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import error_image from '@/public/assets/error.webp';
|
||||
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
|
||||
import RenderLoading from './RenderLoading';
|
||||
|
||||
interface MediaViewerProps {
|
||||
taskObject: TaskObject;
|
||||
@ -573,14 +572,16 @@ export const MediaViewer = React.memo(function MediaViewer({
|
||||
{taskObject.videos.data[currentSketchIndex].video_status !== 1 && (
|
||||
<div className="absolute inset-0 overflow-hidden z-20">
|
||||
{/* 生成中 */}
|
||||
{taskObject.videos.data[currentSketchIndex].video_status === 0 && (
|
||||
renderLoading()
|
||||
{(taskObject.videos.data[currentSketchIndex].video_status === 0 || taskObject.videos.data[currentSketchIndex].video_status === 2) && (
|
||||
<RenderLoading
|
||||
loadingText={taskObject.videos.data[currentSketchIndex].video_status === 0 ? 'Generating video...' : 'Violation of security policy. Please modify your prompt and regenerate.'}
|
||||
isFailed={taskObject.videos.data[currentSketchIndex].video_status === 2}
|
||||
/>
|
||||
)}
|
||||
{/* 生成失败 */}
|
||||
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
|
||||
{/* {taskObject.videos.data[currentSketchIndex].video_status === 2 && (
|
||||
<div className="absolute inset-0 bg-[#fcb0ba1a] flex flex-col items-center justify-center">
|
||||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||||
{/* 文案 */}
|
||||
<div className="text-white text-center">
|
||||
<div className="text-sm font-medium" role="status" aria-live="polite" aria-busy="true">
|
||||
Failed
|
||||
@ -588,7 +589,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
||||
<div className="text-xs text-white/60 mt-1">Violation of security policy. Please modify your prompt and regenerate.</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -778,14 +779,17 @@ export const MediaViewer = React.memo(function MediaViewer({
|
||||
}}
|
||||
>
|
||||
{/* 状态 */}
|
||||
{currentSketch.status === 0 && (
|
||||
renderLoading()
|
||||
{(currentSketch.status === 0 || currentSketch.status === 2) && (
|
||||
<RenderLoading
|
||||
loadingText={currentSketch.status === 0 ? (currentSketch.type === 'role' ? 'Generating role...' : 'Generating scene...') : 'Generate failed'}
|
||||
isFailed={currentSketch.status === 2}
|
||||
/>
|
||||
)}
|
||||
{currentSketch.status === 2 && (
|
||||
{/* {currentSketch.status === 2 && (
|
||||
<div className="absolute inset-0 bg-[#fcb0ba1a] flex items-center justify-center">
|
||||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
||||
{currentSketch.status === 1 && (
|
||||
<AnimatePresence mode="wait">
|
||||
|
||||
@ -5,7 +5,6 @@ import { CircleAlert, Film } from 'lucide-react';
|
||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||
import { getFirstFrame } from '@/utils/tools';
|
||||
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
|
||||
import error_image from '@/public/assets/error.webp';
|
||||
|
||||
interface ThumbnailGridProps {
|
||||
isDisabledFocus: boolean;
|
||||
@ -52,7 +51,7 @@ export function ThumbnailGrid({
|
||||
const [scrollLeft, setScrollLeft] = useState(0);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
// 监听当前选中索引变化,自动滚动到对应位置
|
||||
// 监听当前选中索引变化,自动滚动到对应位置(居中显示)
|
||||
useEffect(() => {
|
||||
if (thumbnailsRef.current) {
|
||||
const container = thumbnailsRef.current;
|
||||
@ -60,9 +59,13 @@ export function ThumbnailGrid({
|
||||
|
||||
if (currentSketchIndex >= 0 && currentSketchIndex < thumbnails.length) {
|
||||
const thumbnail = thumbnails[currentSketchIndex] as HTMLElement;
|
||||
const containerLeft = container.getBoundingClientRect().left;
|
||||
const thumbnailLeft = thumbnail.getBoundingClientRect().left;
|
||||
const scrollPosition = container.scrollLeft + (thumbnailLeft - containerLeft);
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const thumbnailRect = thumbnail.getBoundingClientRect();
|
||||
|
||||
// 计算滚动位置:将缩略图居中显示
|
||||
const containerCenter = containerRect.width / 2;
|
||||
const thumbnailCenter = thumbnailRect.width / 2;
|
||||
const scrollPosition = container.scrollLeft + (thumbnailRect.left - containerRect.left) - containerCenter + thumbnailCenter;
|
||||
|
||||
container.scrollTo({
|
||||
left: scrollPosition,
|
||||
@ -266,7 +269,7 @@ export function ThumbnailGrid({
|
||||
)}
|
||||
{taskObject.videos.data[index].video_status === 2 && (
|
||||
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
|
||||
<img src={error_image.src} alt="error" className="w-6 h-6" />
|
||||
<i className="iconfont icon-shipindiushibaojing text-[#773f8ecc] !text-lg" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -340,7 +343,7 @@ export function ThumbnailGrid({
|
||||
)}
|
||||
{sketch.status === 2 && (
|
||||
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
|
||||
<img src={error_image.src} alt="error" className="w-6 h-6" />
|
||||
<i className="iconfont icon-tudiushi text-[#773f8ecc] !text-lg" />
|
||||
</div>
|
||||
)}
|
||||
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
||||
|
||||
23
iconfont/iconfont.css
Normal file
23
iconfont/iconfont.css
Normal file
@ -0,0 +1,23 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 5039410 */
|
||||
src: url('./iconfont.woff2?t=1760340285776') format('woff2'),
|
||||
url('./iconfont.woff?t=1760340285776') format('woff'),
|
||||
url('./iconfont.ttf?t=1760340285776') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-shipindiushibaojing:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.icon-tudiushi:before {
|
||||
content: "\e717";
|
||||
}
|
||||
|
||||
BIN
iconfont/iconfont.ttf
Normal file
BIN
iconfont/iconfont.ttf
Normal file
Binary file not shown.
BIN
iconfont/iconfont.woff
Normal file
BIN
iconfont/iconfont.woff
Normal file
Binary file not shown.
BIN
iconfont/iconfont.woff2
Normal file
BIN
iconfont/iconfont.woff2
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user