'use client'; import React, { useState, useEffect, useRef, useMemo } from 'react'; // --- Shared Config --- const VIEW_WIDTH = 1000; const VIEW_HEIGHT = 600; // ========================================== // VIEW 4: FILE RECONSTRUCTION (Refined Matrix) // ========================================== const MATRIX_ROWS = 8; const MATRIX_COLS = 10; const TOTAL_BLOCKS = MATRIX_ROWS * MATRIX_COLS; const HEADER_ROWS = 2; const BLOCK_SIZE = 30; const BLOCK_GAP = 4; const HTTP_HEADERS = [ { key: 'Proto', val: 'HTTP/1.1 200 OK', color: '#ffaa00' }, { key: 'Date', val: 'Mon, 27 Jul 2025', color: '#ffaa00' }, { key: 'Content-Type', val: 'image/jpeg', color: '#00ff66' }, // The important one { key: 'Content-Length', val: '24051 bytes', color: '#ffaa00' }, ]; interface MatrixBeam { id: number; startX: number; startY: number; targetX: number; targetY: number; progress: number; color: string; } export const FileReconstructionView = () => { const [scanIndex, setScanIndex] = useState(0); const [beams, setBeams] = useState([]); const [isComplete, setIsComplete] = useState(false); const [revealedHeaders, setRevealedHeaders] = useState([]); const [imageProgress, setImageProgress] = useState(0); // 0 to 1 const requestRef = useRef(undefined); const beamIdRef = useRef(0); const lastTimeRef = useRef(0); // Scene Layout const matrixX = 80; const matrixY = 150; const headerPanelX = 600; const headerPanelY = 100; const imagePanelX = 600; const imagePanelY = 320; const imageWidth = 250; const imageHeight = 180; const animate = (time: number) => { if (lastTimeRef.current === 0) lastTimeRef.current = time; const deltaTime = time - lastTimeRef.current; lastTimeRef.current = time; // 1. Beams Animation setBeams(prev => { return prev.map(b => ({ ...b, progress: b.progress + 0.08 // Beam speed })).filter(b => b.progress < 1); }); // 2. Scanning Logic if (!isComplete) { // Speed control: Headers slow, Body super fast const isHeaderPhase = scanIndex < (HEADER_ROWS * MATRIX_COLS); const tickRate = isHeaderPhase ? 100 : 10; // ms per block // We use a simpler probabilistic approach for "tick" based on framerate // 60fps ~ 16ms. const chance = isHeaderPhase ? 0.1 : 0.9; // Chance to advance scan per frame if (Math.random() < chance && scanIndex < TOTAL_BLOCKS) { // Trigger Scan for current block const row = Math.floor(scanIndex / MATRIX_COLS); const col = scanIndex % MATRIX_COLS; const blockX = matrixX + col * (BLOCK_SIZE + BLOCK_GAP) + BLOCK_SIZE/2; const blockY = matrixY + row * (BLOCK_SIZE + BLOCK_GAP) + BLOCK_SIZE/2; let targetX, targetY, color; if (row < HEADER_ROWS) { // Target Header Panel const headerIdx = Math.floor(scanIndex / 5); // rough mapping 20 blocks -> 4 headers targetX = headerPanelX; targetY = headerPanelY + headerIdx * 30 + 15; color = '#ffaa00'; // Reveal header when beam starts (simplification for instant feedback) or delay? // Let's do it when beam finishes. We simulate delay with timeout or just math. // For visual punch, let's just trigger the header reveal state a bit later. if (!revealedHeaders.includes(headerIdx)) { setTimeout(() => { setRevealedHeaders(prev => [...prev, headerIdx]); }, 300); // Flight time approx } } else { // Target Image targetX = imagePanelX + Math.random() * imageWidth; // Random point on image top targetY = imagePanelY + imageHeight; // Shoot to bottom or center color = '#00ccff'; } // Spawn Beam const newBeam: MatrixBeam = { id: beamIdRef.current++, startX: blockX, startY: blockY, targetX, targetY, progress: 0, color }; setBeams(prev => [...prev, newBeam]); // Increment Scan setScanIndex(prev => prev + 1); // Update Image Progress if (row >= HEADER_ROWS) { const bodyBlocks = TOTAL_BLOCKS - (HEADER_ROWS * MATRIX_COLS); const currentBody = scanIndex - (HEADER_ROWS * MATRIX_COLS); setImageProgress((currentBody + 1) / bodyBlocks); } } if (scanIndex >= TOTAL_BLOCKS) { setIsComplete(true); setTimeout(() => { // Reset Loop setIsComplete(false); setScanIndex(0); setBeams([]); setRevealedHeaders([]); setImageProgress(0); }, 4000); } } requestRef.current = requestAnimationFrame(animate); }; useEffect(() => { requestRef.current = requestAnimationFrame(animate); return () => { if (requestRef.current) cancelAnimationFrame(requestRef.current); }; }, [isComplete, scanIndex]); // Dependencies to ensure closure captures state if needed, though ref handles loop return ( {isComplete ? "文件还原完成" : "正在处理数据流..."} {/* --- Left: Data Matrix --- */} BUFFER MATRIX {Array.from({ length: TOTAL_BLOCKS }).map((_, i) => { const row = Math.floor(i / MATRIX_COLS); const col = i % MATRIX_COLS; const isHeader = row < HEADER_ROWS; const isScanned = i < scanIndex; const x = matrixX + col * (BLOCK_SIZE + BLOCK_GAP); const y = matrixY + row * (BLOCK_SIZE + BLOCK_GAP); let fill = '#1a2333'; if (isScanned) { fill = isHeader ? '#ffaa00' : '#00ccff'; } return ( {/* Adding binary text for detail */} {i % 3 === 0 && isScanned && ( )} ); })} {/* Labels for Rows */} HDR DATA {/* --- Right Top: Header Info --- */} HTTP HEADERS {HTTP_HEADERS.map((h, i) => { const isRevealed = revealedHeaders.includes(i); return ( {h.key}: {h.val} {h.key === 'Content-Type' && isRevealed && ( )} ); })} {/* --- Right Bottom: Image --- */} PAYLOAD PREVIEW {/* Frame */} NO DATA {/* Actual Image (Drawing) */} {/* Scenery */} {/* Scan Line */} {imageProgress > 0 && imageProgress < 1 && ( )} {/* --- Beams (The Light Speed Lines) --- */} {beams.map(b => { const cx = b.startX + (b.targetX - b.startX) * b.progress; const cy = b.startY + (b.targetY - b.startY) * b.progress; // Trail effect const tailX = b.startX + (b.targetX - b.startX) * (Math.max(0, b.progress - 0.1)); const tailY = b.startY + (b.targetY - b.startY) * (Math.max(0, b.progress - 0.1)); return ( ); })} ); };