0.9.9
parent
155eb06f8a
commit
5ae442c4b2
|
|
@ -1,114 +0,0 @@
|
|||
import { useEffect, useState, useRef } from 'react';
|
||||
import { request } from '../utils/request';
|
||||
|
||||
interface DanmakuMessage {
|
||||
id: string;
|
||||
uid: string;
|
||||
username: string;
|
||||
text: string;
|
||||
ts: number;
|
||||
// Runtime properties for animation
|
||||
top?: number;
|
||||
duration?: number;
|
||||
startTime?: number;
|
||||
}
|
||||
|
||||
interface DanmakuLayerProps {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export function DanmakuLayer({ enabled }: DanmakuLayerProps) {
|
||||
const [visibleMessages, setVisibleMessages] = useState<DanmakuMessage[]>([]);
|
||||
const processedIds = useRef<Set<string>>(new Set());
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Polling for new messages
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
const fetchDanmaku = async () => {
|
||||
try {
|
||||
const { data } = await request.get('/danmaku/list');
|
||||
if (Array.isArray(data)) {
|
||||
// Filter out messages we've already seen or processed locally
|
||||
// Actually, for a "live" feel, we only want *recent* messages or messages we haven't shown in this session.
|
||||
// But since the backend returns a 24h window, we don't want to replay all 24h history at once on load.
|
||||
// Strategy: On first load, maybe only show last 20? Or just start listening for new ones?
|
||||
// Let's show recent ones (last 1 min) on load, then polling.
|
||||
|
||||
const now = Date.now() / 1000;
|
||||
const newMessages = data.filter((msg: DanmakuMessage) => {
|
||||
if (processedIds.current.has(msg.id)) return false;
|
||||
// Only show messages from the last 5 minutes to avoid flooding history on reload
|
||||
if (now - msg.ts > 300) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (newMessages.length > 0) {
|
||||
console.log(`[Danmaku] Received ${newMessages.length} new messages`, newMessages);
|
||||
newMessages.forEach((msg: DanmakuMessage) => processedIds.current.add(msg.id));
|
||||
// Add to queue
|
||||
addMessagesToTrack(newMessages);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch danmaku", err);
|
||||
}
|
||||
};
|
||||
|
||||
fetchDanmaku(); // Initial fetch
|
||||
const interval = setInterval(fetchDanmaku, 3000); // Poll every 3s
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [enabled]);
|
||||
|
||||
const addMessagesToTrack = (newMsgs: DanmakuMessage[]) => {
|
||||
// Assign random vertical position and duration
|
||||
const tracks = newMsgs.map(msg => ({
|
||||
...msg,
|
||||
top: Math.floor(Math.random() * 60) + 10, // 10% to 70% height
|
||||
duration: Math.floor(Math.random() * 5) + 8, // 8-13 seconds duration
|
||||
startTime: Date.now()
|
||||
}));
|
||||
|
||||
setVisibleMessages(prev => [...prev, ...tracks]);
|
||||
};
|
||||
|
||||
// Cleanup finished animations
|
||||
const handleAnimationEnd = (id: string) => {
|
||||
setVisibleMessages(prev => prev.filter(m => m.id !== id));
|
||||
};
|
||||
|
||||
if (!enabled) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="absolute inset-0 pointer-events-none z-50 overflow-hidden"
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
{visibleMessages.map(msg => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className="absolute whitespace-nowrap text-white font-bold text-shadow-md will-change-transform"
|
||||
style={{
|
||||
top: `${msg.top}%`,
|
||||
left: '100%',
|
||||
fontSize: '1.5rem', // Increased size for visibility
|
||||
textShadow: '0 0 4px rgba(0,0,0,0.8), 0 0 2px rgba(0,0,0,1)', // Stronger shadow
|
||||
animation: `danmaku-move ${msg.duration}s linear forwards`
|
||||
}}
|
||||
onAnimationEnd={() => handleAnimationEnd(msg.id)}
|
||||
>
|
||||
{msg.text}
|
||||
</div>
|
||||
))}
|
||||
<style>{`
|
||||
@keyframes danmaku-move {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-100vw - 100%); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -41,9 +41,15 @@ export function MessageBoard({ open, onClose }: MessageBoardProps) {
|
|||
fetchMessages().finally(() => setLoading(false));
|
||||
intervalRef.current = window.setInterval(fetchMessages, 3000);
|
||||
} else {
|
||||
clearInterval(intervalRef.current);
|
||||
if (intervalRef.current !== null) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
}
|
||||
return () => clearInterval(intervalRef.current);
|
||||
return () => {
|
||||
if (intervalRef.current !== null) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ export function TerminalModal({
|
|||
}
|
||||
.terminal-modal .ant-modal-close {
|
||||
color: #2ea043 !important;
|
||||
top: 12px !important;
|
||||
}
|
||||
.terminal-modal .ant-modal-close:hover {
|
||||
background-color: rgba(35, 134, 54, 0.2) !important;
|
||||
|
|
|
|||
Loading…
Reference in New Issue