forked from 77media/video-flow
177 lines
5.0 KiB
TypeScript
177 lines
5.0 KiB
TypeScript
import { notification, Progress } from 'antd';
|
||
import { useEffect, useRef, useState, useMemo } from 'react';
|
||
import { Scissors } from 'lucide-react';
|
||
import { motion } from 'framer-motion';
|
||
|
||
// 暗色玻璃风格样式
|
||
const darkGlassStyle = {
|
||
background: 'rgba(30, 32, 40, 0.95)',
|
||
backdropFilter: 'blur(10px)',
|
||
WebkitBackdropFilter: 'blur(10px)',
|
||
border: '1px solid rgba(255, 255, 255, 0.08)',
|
||
borderRadius: '8px',
|
||
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.4)',
|
||
padding: '12px 16px',
|
||
};
|
||
|
||
const messageStyle = {
|
||
fontSize: '13px',
|
||
fontWeight: 500,
|
||
color: '#ffffff',
|
||
marginBottom: '6px',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '6px',
|
||
};
|
||
|
||
const descriptionStyle = {
|
||
fontSize: '12px',
|
||
color: 'rgba(255, 255, 255, 0.65)',
|
||
marginBottom: '12px',
|
||
lineHeight: '1.5',
|
||
};
|
||
|
||
interface EditingNotificationProps {
|
||
/** 编辑是否完成 */
|
||
isCompleted?: boolean;
|
||
/** 自定义标题 */
|
||
title?: string;
|
||
/** 自定义描述 */
|
||
description?: string;
|
||
/** key */
|
||
key?: string;
|
||
/** 完成时的回调 */
|
||
onComplete?: () => void;
|
||
/** 失败时的回调 */
|
||
onFail?: () => void;
|
||
}
|
||
|
||
/**
|
||
* 显示视频编辑进度通知
|
||
* @param props EditingNotificationProps
|
||
*/
|
||
export const showEditingNotification = (props: EditingNotificationProps) => {
|
||
const {
|
||
isCompleted = false,
|
||
title = 'AI Video Editing',
|
||
description = 'Your video is being edited by AI...',
|
||
key,
|
||
onComplete,
|
||
onFail,
|
||
} = props;
|
||
|
||
const NotificationContent = () => {
|
||
const [progress, setProgress] = useState(0);
|
||
const [status, setStatus] = useState<'active' | 'success' | 'exception'>('active');
|
||
const [currentDescription, setCurrentDescription] = useState(description);
|
||
const timerRef = useRef<NodeJS.Timeout>();
|
||
const startTimeRef = useRef(Date.now());
|
||
|
||
const scissorsIcon = useMemo(() => (
|
||
<motion.div
|
||
style={{ display: 'inline-flex', marginRight: '8px' }}
|
||
animate={status === 'active' ? {
|
||
rotate: [0, 360],
|
||
scale: [1, 1.2, 1]
|
||
} : { rotate: 0, scale: 1 }}
|
||
transition={status === 'active' ? {
|
||
rotate: { duration: 3, repeat: Infinity, ease: "linear" },
|
||
scale: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
|
||
} : { duration: 0.3 }}
|
||
>
|
||
<Scissors className="w-5 h-5 text-[#f59e0b]" />
|
||
</motion.div>
|
||
), [status]);
|
||
|
||
// 处理进度更新
|
||
useEffect(() => {
|
||
const updateProgress = () => {
|
||
const elapsed = Date.now() - startTimeRef.current;
|
||
const timeLimit = 10 * 60 * 1000; // 10分钟
|
||
|
||
if (isCompleted) {
|
||
// 如果完成了,快速增加到100%
|
||
setProgress(prev => {
|
||
const next = prev + (100 - prev) / 10;
|
||
if (next >= 99.9) {
|
||
setStatus('success');
|
||
setCurrentDescription('编辑完成,已更新到页面中');
|
||
onComplete?.();
|
||
clearInterval(timerRef.current);
|
||
return 100;
|
||
}
|
||
return next;
|
||
});
|
||
} else if (elapsed >= timeLimit) {
|
||
// 超时失败
|
||
setStatus('exception');
|
||
setCurrentDescription('编辑超时,请重试');
|
||
onFail?.();
|
||
clearInterval(timerRef.current);
|
||
return;
|
||
} else {
|
||
// 正常进度,缓慢增加到90%
|
||
setProgress(prev => {
|
||
const targetProgress = (elapsed / timeLimit) * 90;
|
||
const next = Math.min(prev + 0.5, targetProgress);
|
||
return next;
|
||
});
|
||
}
|
||
};
|
||
|
||
timerRef.current = setInterval(updateProgress, 100);
|
||
return () => clearInterval(timerRef.current);
|
||
}, [isCompleted]);
|
||
|
||
return (
|
||
<div data-alt="editing-notification" style={{ minWidth: '300px' }}>
|
||
<h3 style={messageStyle}>
|
||
{scissorsIcon}
|
||
{title}
|
||
</h3>
|
||
<p style={{
|
||
...descriptionStyle,
|
||
color: status === 'exception' ? '#ff4d4f' :
|
||
'rgba(255, 255, 255, 0.65)',
|
||
}}>{currentDescription}</p>
|
||
<Progress
|
||
percent={Math.round(progress)}
|
||
status={status}
|
||
strokeColor={{
|
||
'0%': '#f59e0b',
|
||
'100%': '#a855f7',
|
||
}}
|
||
trailColor="rgba(255,255,255,0.08)"
|
||
size="small"
|
||
format={percent => (
|
||
<span style={{
|
||
color: status === 'exception' ? '#ff4d4f' :
|
||
status === 'success' ? '#a855f7' :
|
||
'rgba(255,255,255,0.85)',
|
||
fontSize: '12px',
|
||
fontWeight: 500
|
||
}}>
|
||
{`${percent}%`}
|
||
</span>
|
||
)}
|
||
className="transition-all duration-300 ease-in-out"
|
||
/>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
notification.open({
|
||
key,
|
||
message: null,
|
||
description: <NotificationContent />,
|
||
duration: 0,
|
||
placement: 'topRight',
|
||
style: darkGlassStyle,
|
||
className: 'dark-glass-notification',
|
||
closeIcon: null
|
||
});
|
||
|
||
// 返回key以便外部可以手动关闭通知
|
||
return key;
|
||
};
|