Files
mixvideo-v2/apps/desktop/src/components/canvas/NotificationSystem.tsx
imeepos 7d8b8a3de1 feat: 实现AI画布工具并隐藏,设置项目为首页
- 新增完整的AI画布工具系统
  - 可视化节点编辑器,支持拖拽连线
  - 多种节点类型:文本输入、图片生成、视频生成等
  - 智能连接验证和数据流转换
  - 异步处理引擎,支持进度追踪和取消
  - 批量处理系统,支持并发处理
  - AI服务集成框架,支持多种AI API

- 用户体验优化
  - 智能弹框定位,防止被遮挡
  - 节点删除功能(悬停删除按钮 + 键盘快捷键)
  - 通知系统和错误处理
  - 快速开始模板
  - 键盘快捷键支持

- 界面调整
  - 暂时隐藏AI画布,保留代码
  - 设置项目列表为首页
  - 简化导航栏结构
2025-08-07 10:12:46 +08:00

249 lines
6.7 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { CheckCircle, XCircle, AlertCircle, Info, X } from 'lucide-react';
export type NotificationType = 'success' | 'error' | 'warning' | 'info';
export interface Notification {
id: string;
type: NotificationType;
title: string;
message?: string;
duration?: number;
action?: {
label: string;
onClick: () => void;
};
}
interface NotificationSystemProps {
notifications: Notification[];
onRemove: (id: string) => void;
}
const NotificationItem: React.FC<{
notification: Notification;
onRemove: (id: string) => void;
}> = ({ notification, onRemove }) => {
const [isVisible, setIsVisible] = useState(false);
const [isLeaving, setIsLeaving] = useState(false);
useEffect(() => {
// 入场动画
setTimeout(() => setIsVisible(true), 50);
// 自动移除
if (notification.duration !== 0) {
const timer = setTimeout(() => {
handleRemove();
}, notification.duration || 5000);
return () => clearTimeout(timer);
}
}, [notification.duration]);
const handleRemove = () => {
setIsLeaving(true);
setTimeout(() => {
onRemove(notification.id);
}, 300);
};
const getIcon = () => {
switch (notification.type) {
case 'success':
return <CheckCircle className="w-5 h-5 text-green-500" />;
case 'error':
return <XCircle className="w-5 h-5 text-red-500" />;
case 'warning':
return <AlertCircle className="w-5 h-5 text-yellow-500" />;
case 'info':
return <Info className="w-5 h-5 text-blue-500" />;
}
};
const getBackgroundColor = () => {
switch (notification.type) {
case 'success':
return 'bg-green-50 border-green-200';
case 'error':
return 'bg-red-50 border-red-200';
case 'warning':
return 'bg-yellow-50 border-yellow-200';
case 'info':
return 'bg-blue-50 border-blue-200';
}
};
return (
<div
className={`
transform transition-all duration-300 ease-in-out
${isVisible && !isLeaving ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}
${getBackgroundColor()}
border rounded-lg p-4 shadow-lg max-w-sm w-full
`}
>
<div className="flex items-start space-x-3">
<div className="flex-shrink-0">
{getIcon()}
</div>
<div className="flex-1 min-w-0">
<h4 className="text-sm font-medium text-gray-900">
{notification.title}
</h4>
{notification.message && (
<p className="text-sm text-gray-600 mt-1">
{notification.message}
</p>
)}
{notification.action && (
<button
onClick={notification.action.onClick}
className="text-sm font-medium text-blue-600 hover:text-blue-800 mt-2 transition-colors"
>
{notification.action.label}
</button>
)}
</div>
<button
onClick={handleRemove}
className="flex-shrink-0 text-gray-400 hover:text-gray-600 transition-colors"
>
<X className="w-4 h-4" />
</button>
</div>
</div>
);
};
export const NotificationSystem: React.FC<NotificationSystemProps> = ({
notifications,
onRemove
}) => {
return (
<div className="fixed top-4 right-4 z-50 space-y-2">
{notifications.map(notification => (
<NotificationItem
key={notification.id}
notification={notification}
onRemove={onRemove}
/>
))}
</div>
);
};
// Hook for managing notifications
export const useNotifications = () => {
const [notifications, setNotifications] = useState<Notification[]>([]);
const addNotification = (notification: Omit<Notification, 'id'>) => {
const id = `notification-${Date.now()}-${Math.random()}`;
setNotifications(prev => [...prev, { ...notification, id }]);
};
const removeNotification = (id: string) => {
setNotifications(prev => prev.filter(n => n.id !== id));
};
const clearAll = () => {
setNotifications([]);
};
// 便捷方法
const success = (title: string, message?: string, options?: Partial<Notification>) => {
addNotification({ type: 'success', title, message, ...options });
};
const error = (title: string, message?: string, options?: Partial<Notification>) => {
addNotification({ type: 'error', title, message, duration: 0, ...options });
};
const warning = (title: string, message?: string, options?: Partial<Notification>) => {
addNotification({ type: 'warning', title, message, ...options });
};
const info = (title: string, message?: string, options?: Partial<Notification>) => {
addNotification({ type: 'info', title, message, ...options });
};
return {
notifications,
addNotification,
removeNotification,
clearAll,
success,
error,
warning,
info
};
};
// 全局通知管理器
class GlobalNotificationManager {
private listeners: Array<(notifications: Notification[]) => void> = [];
private notifications: Notification[] = [];
subscribe(listener: (notifications: Notification[]) => void) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
private notify() {
this.listeners.forEach(listener => listener([...this.notifications]));
}
add(notification: Omit<Notification, 'id'>) {
const id = `global-notification-${Date.now()}-${Math.random()}`;
this.notifications.push({ ...notification, id });
this.notify();
}
remove(id: string) {
this.notifications = this.notifications.filter(n => n.id !== id);
this.notify();
}
clear() {
this.notifications = [];
this.notify();
}
success(title: string, message?: string, options?: Partial<Notification>) {
this.add({ type: 'success', title, message, ...options });
}
error(title: string, message?: string, options?: Partial<Notification>) {
this.add({ type: 'error', title, message, duration: 0, ...options });
}
warning(title: string, message?: string, options?: Partial<Notification>) {
this.add({ type: 'warning', title, message, ...options });
}
info(title: string, message?: string, options?: Partial<Notification>) {
this.add({ type: 'info', title, message, ...options });
}
}
export const globalNotifications = new GlobalNotificationManager();
// Hook for global notifications
export const useGlobalNotifications = () => {
const [notifications, setNotifications] = useState<Notification[]>([]);
useEffect(() => {
return globalNotifications.subscribe(setNotifications);
}, []);
return {
notifications,
removeNotification: globalNotifications.remove.bind(globalNotifications)
};
};