import { useEffect, useState, useRef } from 'react' import { NotificationToast, type NotificationData } from '../components/NotificationToast' import '../components/NotificationToast.scss' import './NotificationWindow.scss' export default function NotificationWindow() { const [notification, setNotification] = useState(null) const [prevNotification, setPrevNotification] = useState(null) const [position, setPosition] = useState('top-right') // We need a ref to access the current notification inside the callback // without satisfying the dependency array which would recreate the listener // Actually, setNotification(prev => ...) pattern is better, but we need the VALUE of current to set as prev. // So we use setNotification callback: setNotification(current => { ... return newNode }) // But we need to update TWO states. // So we use a ref to track "current displayed" for the event handler. // Or just use functional updates, but we need to setPrev(current). const notificationRef = useRef(null) useEffect(() => { notificationRef.current = notification }, [notification]) useEffect(() => { const handleShow = (_event: any, data: any) => { // data: { title, content, avatarUrl, sessionId } const timestamp = Math.floor(Date.now() / 1000) const newNoti: NotificationData = { id: `noti_${timestamp}_${Math.random().toString(36).substr(2, 9)}`, sessionId: data.sessionId, title: data.title, content: data.content, timestamp: timestamp, avatarUrl: data.avatarUrl } // 获取位置配置 if (data.position) { setPosition(data.position) } // Set previous to current (ref) if (notificationRef.current) { setPrevNotification(notificationRef.current) } setNotification(newNoti) } if (window.electronAPI) { const remove = window.electronAPI.notification?.onShow?.(handleShow) window.electronAPI.notification?.ready?.() return () => remove?.() } }, []) // Clean up prevNotification after transition useEffect(() => { if (prevNotification) { const timer = setTimeout(() => { setPrevNotification(null) }, 400) return () => clearTimeout(timer) } }, [prevNotification]) const handleClose = () => { setNotification(null) setPrevNotification(null) window.electronAPI.notification?.close() } const handleClick = (sessionId: string) => { window.electronAPI.notification?.click(sessionId) setNotification(null) setPrevNotification(null) // Main process handles window hide/close } useEffect(() => { // Measure only if we have a notification (current or prev) if (!notification && !prevNotification) return // Prefer measuring the NEW one const targetId = notification ? 'notification-current' : 'notification-prev' const timer = setTimeout(() => { // Find the wrapper of the content // Since we wrap them, we should measure the content inside // But getting root is easier if size is set by relative child const root = document.getElementById('notification-root') if (root) { const height = root.offsetHeight const width = 344 if (window.electronAPI?.notification?.resize) { const finalHeight = Math.min(height + 4, 300) window.electronAPI.notification.resize(width, finalHeight) } } }, 50) return () => clearTimeout(timer) }, [notification, prevNotification]) if (!notification && !prevNotification) return null return (
{/* Previous Notification (Background / Fading Out) */} {prevNotification && (
{ }} // No-op for background item onClick={() => { }} position={position as any} isStatic={true} initialVisible={true} />
)} {/* Current Notification (Foreground / Fading In) */} {notification && (
)}
) }