forked from 77media/video-flow
loading动画以及错误展示
This commit is contained in:
parent
d16970c45b
commit
568cb65cf3
@ -1,5 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
import '@/iconfont/iconfont.css';
|
||||||
import { createContext, useContext, useEffect, useState } from 'react';
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
import { Providers } from '@/components/providers';
|
import { Providers } from '@/components/providers';
|
||||||
import { ConfigProvider, theme } from 'antd';
|
import { ConfigProvider, theme } from 'antd';
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { Drawer } from 'antd';
|
|||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import error_image from '@/public/assets/error.webp';
|
import error_image from '@/public/assets/error.webp';
|
||||||
import { showDownloadOptionsModal } from './download-options-modal';
|
import { showDownloadOptionsModal } from './download-options-modal';
|
||||||
|
import RenderLoading from './RenderLoading';
|
||||||
|
|
||||||
interface H5MediaViewerProps {
|
interface H5MediaViewerProps {
|
||||||
/** 任务对象,包含各阶段数据 */
|
/** 任务对象,包含各阶段数据 */
|
||||||
@ -150,9 +151,10 @@ export function H5MediaViewer({
|
|||||||
return [...roles, ...scenes].map(item => ({
|
return [...roles, ...scenes].map(item => ({
|
||||||
url: item?.url as string | undefined,
|
url: item?.url as string | undefined,
|
||||||
status: item?.status as number | 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]);
|
}, [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={{
|
<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,
|
height: videoWrapperHeight,
|
||||||
}}>
|
}}>
|
||||||
{status === 0 && (
|
{(status === 0 || status === 2) && (
|
||||||
<span className="text-blue-500 text-base">Generating...</span>
|
<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">
|
<div className="flex flex-col items-center justify-center gap-1">
|
||||||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||||||
<span className="text-[#fa485f] text-base">Generate failed</span>
|
<span className="text-[#fa485f] text-base">Generate failed</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
{status !== 0 && status !== 2 && (
|
|
||||||
<span className="text-white/70 text-base">Pending</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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={{
|
<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,
|
height: imageWrapperHeight,
|
||||||
}}>
|
}}>
|
||||||
{status === 0 && (
|
{(status === 0 || status === 2) && (
|
||||||
<span className="text-blue-500 text-base">Generating...</span>
|
<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">
|
<div className="flex flex-col items-center justify-center gap-1">
|
||||||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||||||
<span className="text-[#fa485f] text-base">Generate failed</span>
|
<span className="text-[#fa485f] text-base">Generate failed</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -544,8 +551,9 @@ export function H5MediaViewer({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<style jsx global>{`
|
<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-slider { height: 100% !important; }
|
||||||
|
.slick-current > div { width: 100% !important; }
|
||||||
.ant-carousel { height: 100% !important; }
|
.ant-carousel { height: 100% !important; }
|
||||||
.slick-list { width: 100%;height: 100% !important;max-height: 100%; }
|
.slick-list { width: 100%;height: 100% !important;max-height: 100%; }
|
||||||
.slick-track { display: flex !important; align-items: center;height: 100% !important; }
|
.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 { EditPoint as EditPointType } from './video-edit/types';
|
||||||
import { isVideoModificationEnabled } from '@/lib/server-config';
|
import { isVideoModificationEnabled } from '@/lib/server-config';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import error_image from '@/public/assets/error.webp';
|
import RenderLoading from './RenderLoading';
|
||||||
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
|
|
||||||
|
|
||||||
interface MediaViewerProps {
|
interface MediaViewerProps {
|
||||||
taskObject: TaskObject;
|
taskObject: TaskObject;
|
||||||
@ -573,14 +572,16 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
{taskObject.videos.data[currentSketchIndex].video_status !== 1 && (
|
{taskObject.videos.data[currentSketchIndex].video_status !== 1 && (
|
||||||
<div className="absolute inset-0 overflow-hidden z-20">
|
<div className="absolute inset-0 overflow-hidden z-20">
|
||||||
{/* 生成中 */}
|
{/* 生成中 */}
|
||||||
{taskObject.videos.data[currentSketchIndex].video_status === 0 && (
|
{(taskObject.videos.data[currentSketchIndex].video_status === 0 || taskObject.videos.data[currentSketchIndex].video_status === 2) && (
|
||||||
renderLoading()
|
<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">
|
<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" />
|
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||||||
{/* 文案 */}
|
|
||||||
<div className="text-white text-center">
|
<div className="text-white text-center">
|
||||||
<div className="text-sm font-medium" role="status" aria-live="polite" aria-busy="true">
|
<div className="text-sm font-medium" role="status" aria-live="polite" aria-busy="true">
|
||||||
Failed
|
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 className="text-xs text-white/60 mt-1">Violation of security policy. Please modify your prompt and regenerate.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -778,14 +779,17 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 状态 */}
|
{/* 状态 */}
|
||||||
{currentSketch.status === 0 && (
|
{(currentSketch.status === 0 || currentSketch.status === 2) && (
|
||||||
renderLoading()
|
<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">
|
<div className="absolute inset-0 bg-[#fcb0ba1a] flex items-center justify-center">
|
||||||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
||||||
{currentSketch.status === 1 && (
|
{currentSketch.status === 1 && (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { CircleAlert, Film } from 'lucide-react';
|
|||||||
import { TaskObject } from '@/api/DTO/movieEdit';
|
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||||
import { getFirstFrame } from '@/utils/tools';
|
import { getFirstFrame } from '@/utils/tools';
|
||||||
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
|
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
|
||||||
import error_image from '@/public/assets/error.webp';
|
|
||||||
|
|
||||||
interface ThumbnailGridProps {
|
interface ThumbnailGridProps {
|
||||||
isDisabledFocus: boolean;
|
isDisabledFocus: boolean;
|
||||||
@ -52,7 +51,7 @@ export function ThumbnailGrid({
|
|||||||
const [scrollLeft, setScrollLeft] = useState(0);
|
const [scrollLeft, setScrollLeft] = useState(0);
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
// 监听当前选中索引变化,自动滚动到对应位置
|
// 监听当前选中索引变化,自动滚动到对应位置(居中显示)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (thumbnailsRef.current) {
|
if (thumbnailsRef.current) {
|
||||||
const container = thumbnailsRef.current;
|
const container = thumbnailsRef.current;
|
||||||
@ -60,9 +59,13 @@ export function ThumbnailGrid({
|
|||||||
|
|
||||||
if (currentSketchIndex >= 0 && currentSketchIndex < thumbnails.length) {
|
if (currentSketchIndex >= 0 && currentSketchIndex < thumbnails.length) {
|
||||||
const thumbnail = thumbnails[currentSketchIndex] as HTMLElement;
|
const thumbnail = thumbnails[currentSketchIndex] as HTMLElement;
|
||||||
const containerLeft = container.getBoundingClientRect().left;
|
const containerRect = container.getBoundingClientRect();
|
||||||
const thumbnailLeft = thumbnail.getBoundingClientRect().left;
|
const thumbnailRect = thumbnail.getBoundingClientRect();
|
||||||
const scrollPosition = container.scrollLeft + (thumbnailLeft - containerLeft);
|
|
||||||
|
// 计算滚动位置:将缩略图居中显示
|
||||||
|
const containerCenter = containerRect.width / 2;
|
||||||
|
const thumbnailCenter = thumbnailRect.width / 2;
|
||||||
|
const scrollPosition = container.scrollLeft + (thumbnailRect.left - containerRect.left) - containerCenter + thumbnailCenter;
|
||||||
|
|
||||||
container.scrollTo({
|
container.scrollTo({
|
||||||
left: scrollPosition,
|
left: scrollPosition,
|
||||||
@ -266,7 +269,7 @@ export function ThumbnailGrid({
|
|||||||
)}
|
)}
|
||||||
{taskObject.videos.data[index].video_status === 2 && (
|
{taskObject.videos.data[index].video_status === 2 && (
|
||||||
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -340,7 +343,7 @@ export function ThumbnailGrid({
|
|||||||
)}
|
)}
|
||||||
{sketch.status === 2 && (
|
{sketch.status === 2 && (
|
||||||
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
{/* 只在生成过程中或没有分镜图片时使用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