Skip to content

Commit 8b2eceb

Browse files
committed
feat: Add EncapsulatedPacket component
1 parent 4355f1c commit 8b2eceb

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

src/components/EncapsulatedPacket.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { useMemo } from 'react'
2+
import { TCP_IP_LAYERS } from '@/data'
3+
import { Mail } from 'lucide-react'
4+
import type { PacketStep } from '@/utils/getPacketForStep'
5+
6+
interface Props {
7+
packet: PacketStep
8+
stage: number
9+
}
10+
11+
function EncapsulatedPacket(props: Props) {
12+
const visibleHeaders = useMemo(() => {
13+
const visibleHeaders: number[] = []
14+
15+
if (props.stage >= 1 && props.stage <= 7) visibleHeaders.push(1) // Transport header
16+
if (props.stage >= 2 && props.stage <= 6) visibleHeaders.push(2) // Internet header
17+
if (props.stage >= 3 && props.stage <= 5) visibleHeaders.push(3) // Network Interface header
18+
19+
return visibleHeaders
20+
}, [props.stage])
21+
22+
const currentPosition = useMemo(() => {
23+
const isClientToServer = props.packet.from === 'client'
24+
const clientX = 16
25+
const serverX = 84
26+
27+
// Calculate positions for each stage
28+
const positions = [
29+
{ x: isClientToServer ? clientX : serverX, y: 160 }, // Application
30+
{ x: isClientToServer ? clientX : serverX, y: 260 }, // Transport
31+
{ x: isClientToServer ? clientX : serverX, y: 360 }, // Internet
32+
{ x: isClientToServer ? clientX : serverX, y: 460 }, // Network Interface
33+
{
34+
x: isClientToServer ? (clientX + serverX) / 2 : (serverX + clientX) / 2,
35+
y: 500,
36+
}, // Transmission
37+
{ x: isClientToServer ? serverX : clientX, y: 460 }, // Network Interface (receiver)
38+
{ x: isClientToServer ? serverX : clientX, y: 360 }, // Internet (receiver)
39+
{ x: isClientToServer ? serverX : clientX, y: 260 }, // Transport (receiver)
40+
{ x: isClientToServer ? serverX : clientX, y: 160 }, // Application (receiver)
41+
]
42+
43+
// Get position for current stage
44+
const position = positions[props.stage]
45+
46+
return position
47+
}, [props.stage, props.packet.from])
48+
49+
return (
50+
<div
51+
className="absolute transition-all duration-500"
52+
style={{
53+
left: `${currentPosition.x}%`,
54+
top: currentPosition.y,
55+
transform: 'translate(-50%, -50%)',
56+
zIndex: 50,
57+
}}
58+
>
59+
{/* Base packet (envelope) */}
60+
<div className="relative">
61+
{/* Data payload (envelope) */}
62+
<div className="w-16 h-12 bg-white border-2 border-black rounded flex items-center justify-center">
63+
<Mail className="w-8 h-8 text-black" />
64+
</div>
65+
66+
{/* Layer headers (colored borders) */}
67+
{visibleHeaders.map((layerIndex) => (
68+
<div
69+
key={`header-${layerIndex}`}
70+
className="absolute inset-0 border-4 rounded"
71+
style={{
72+
borderColor: TCP_IP_LAYERS[layerIndex].color,
73+
transform: `scale(${1 + (visibleHeaders.length - visibleHeaders.indexOf(layerIndex)) * 0.15})`,
74+
zIndex: -layerIndex,
75+
}}
76+
>
77+
{/* Header label */}
78+
<div
79+
className="absolute -top-5 left-1/2 transform -translate-x-1/2 text-[8px] font-bold px-1 rounded"
80+
style={{
81+
backgroundColor: TCP_IP_LAYERS[layerIndex].color,
82+
color: 'white',
83+
}}
84+
>
85+
{TCP_IP_LAYERS[layerIndex].header}
86+
</div>
87+
</div>
88+
))}
89+
90+
{/* Packet type label */}
91+
<div
92+
className="absolute -bottom-5 left-1/2 transform -translate-x-1/2 text-[10px] font-bold px-1 rounded"
93+
style={{ backgroundColor: props.packet.type.color, color: 'white' }}
94+
>
95+
{props.packet.type.name}
96+
</div>
97+
</div>
98+
</div>
99+
)
100+
}
101+
102+
export default EncapsulatedPacket

0 commit comments

Comments
 (0)