275 lines
9.2 KiB
Rust
275 lines
9.2 KiB
Rust
use common_models::CircleData;
|
|
use gloo_timers::callback::{Interval, Timeout};
|
|
use rand::seq::SliceRandom;
|
|
use rand::Rng;
|
|
use std::collections::HashMap;
|
|
use std::rc::Rc;
|
|
use yew::prelude::*;
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
struct ServerNode {
|
|
x: f32,
|
|
y: f32,
|
|
name: String,
|
|
id: u32,
|
|
is_active: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
struct DataTransmission {
|
|
id: usize,
|
|
from_node: u32,
|
|
to_node: u32,
|
|
progress: f32,
|
|
transmission_type: TransmissionType,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
enum TransmissionType {
|
|
Data,
|
|
Sync,
|
|
Heartbeat,
|
|
}
|
|
|
|
#[derive(Properties, Clone, PartialEq)]
|
|
pub struct NetworkAnimationViewProps {
|
|
pub all_circles: Rc<HashMap<u32, CircleData>>,
|
|
}
|
|
|
|
pub enum Msg {
|
|
StartTransmission,
|
|
UpdateTransmissions,
|
|
RemoveTransmission(usize),
|
|
PulseNode(u32),
|
|
}
|
|
|
|
pub struct NetworkAnimationView {
|
|
server_nodes: Rc<HashMap<u32, ServerNode>>,
|
|
active_transmissions: Vec<DataTransmission>,
|
|
next_transmission_id: usize,
|
|
_transmission_interval: Option<Interval>,
|
|
_update_interval: Option<Interval>,
|
|
}
|
|
|
|
impl NetworkAnimationView {
|
|
fn calculate_server_positions(
|
|
all_circles: &Rc<HashMap<u32, CircleData>>,
|
|
) -> Rc<HashMap<u32, ServerNode>> {
|
|
let mut nodes = HashMap::new();
|
|
|
|
// Predefined realistic server locations on the world map (coordinates scaled to viewBox 783.086 x 400.649)
|
|
let server_positions = vec![
|
|
(180.0, 150.0, "North America"), // USA/Canada
|
|
(420.0, 130.0, "Europe"), // Central Europe
|
|
(580.0, 160.0, "Asia"), // East Asia
|
|
(220.0, 280.0, "South America"), // Brazil/Argentina
|
|
(450.0, 220.0, "Africa"), // Central Africa
|
|
(650.0, 320.0, "Oceania"), // Australia
|
|
(400.0, 90.0, "Nordic"), // Scandinavia
|
|
(520.0, 200.0, "Middle East"), // Middle East
|
|
];
|
|
|
|
for (i, (id, circle_data)) in all_circles.iter().enumerate() {
|
|
if let Some((x, y, _region)) = server_positions.get(i % server_positions.len()) {
|
|
nodes.insert(
|
|
*id,
|
|
ServerNode {
|
|
x: *x,
|
|
y: *y,
|
|
name: format!("{}", circle_data.name),
|
|
id: *id,
|
|
is_active: true,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
Rc::new(nodes)
|
|
}
|
|
|
|
fn create_transmission(
|
|
&mut self,
|
|
from_id: u32,
|
|
to_id: u32,
|
|
transmission_type: TransmissionType,
|
|
) -> usize {
|
|
let id = self.next_transmission_id;
|
|
self.next_transmission_id += 1;
|
|
|
|
self.active_transmissions.push(DataTransmission {
|
|
id,
|
|
from_node: from_id,
|
|
to_node: to_id,
|
|
progress: 0.0,
|
|
transmission_type,
|
|
});
|
|
|
|
id
|
|
}
|
|
}
|
|
|
|
impl Component for NetworkAnimationView {
|
|
type Message = Msg;
|
|
type Properties = NetworkAnimationViewProps;
|
|
|
|
fn create(ctx: &Context<Self>) -> Self {
|
|
let server_nodes = Self::calculate_server_positions(&ctx.props().all_circles);
|
|
|
|
let link = ctx.link().clone();
|
|
let transmission_interval = Interval::new(3000, move || {
|
|
link.send_message(Msg::StartTransmission);
|
|
});
|
|
|
|
let link2 = ctx.link().clone();
|
|
let update_interval = Interval::new(50, move || {
|
|
link2.send_message(Msg::UpdateTransmissions);
|
|
});
|
|
|
|
Self {
|
|
server_nodes,
|
|
active_transmissions: Vec::new(),
|
|
next_transmission_id: 0,
|
|
_transmission_interval: Some(transmission_interval),
|
|
_update_interval: Some(update_interval),
|
|
}
|
|
}
|
|
|
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
match msg {
|
|
Msg::StartTransmission => {
|
|
if self.server_nodes.len() < 2 {
|
|
return false;
|
|
}
|
|
|
|
let mut rng = rand::thread_rng();
|
|
let node_ids: Vec<u32> = self.server_nodes.keys().cloned().collect();
|
|
|
|
if let (Some(&from_id), Some(&to_id)) =
|
|
(node_ids.choose(&mut rng), node_ids.choose(&mut rng))
|
|
{
|
|
if from_id != to_id {
|
|
let transmission_type = match rng.gen_range(0..3) {
|
|
0 => TransmissionType::Data,
|
|
1 => TransmissionType::Sync,
|
|
_ => TransmissionType::Heartbeat,
|
|
};
|
|
|
|
let transmission_id =
|
|
self.create_transmission(from_id, to_id, transmission_type);
|
|
|
|
// Pulse the source node
|
|
ctx.link().send_message(Msg::PulseNode(from_id));
|
|
|
|
// Remove transmission after completion
|
|
let link = ctx.link().clone();
|
|
let timeout = Timeout::new(2000, move || {
|
|
link.send_message(Msg::RemoveTransmission(transmission_id));
|
|
});
|
|
timeout.forget();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
Msg::UpdateTransmissions => {
|
|
let mut updated = false;
|
|
for transmission in &mut self.active_transmissions {
|
|
if transmission.progress < 1.0 {
|
|
transmission.progress += 0.02; // 2% per update (50ms * 50 = 2.5s total)
|
|
updated = true;
|
|
}
|
|
}
|
|
updated
|
|
}
|
|
Msg::RemoveTransmission(id) => {
|
|
let initial_len = self.active_transmissions.len();
|
|
self.active_transmissions.retain(|t| t.id != id);
|
|
self.active_transmissions.len() != initial_len
|
|
}
|
|
Msg::PulseNode(_node_id) => {
|
|
// This will trigger a re-render for node pulse animation
|
|
true
|
|
}
|
|
}
|
|
}
|
|
|
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
|
let server_pins = self.server_nodes.iter().map(|(_id, node)| {
|
|
html! {
|
|
<g class="server-node" transform={format!("translate({}, {})", node.x, node.y)}>
|
|
// Subtle glow background
|
|
<circle r="6" class="node-glow" />
|
|
// Main white pin
|
|
<circle r="3" class="node-pin" />
|
|
// Ultra-subtle breathing effect
|
|
<circle r="4" class="node-pulse" />
|
|
// Clean label
|
|
<text x="0" y="16" class="node-label" text-anchor="middle">{&node.name}</text>
|
|
</g>
|
|
}
|
|
});
|
|
|
|
let transmissions = self
|
|
.active_transmissions
|
|
.iter()
|
|
.map(|transmission| {
|
|
if let (Some(from_node), Some(to_node)) = (
|
|
self.server_nodes.get(&transmission.from_node),
|
|
self.server_nodes.get(&transmission.to_node),
|
|
) {
|
|
html! {
|
|
<g class="transmission-group">
|
|
// Simple connection line with subtle animation
|
|
<line
|
|
x1={from_node.x.to_string()}
|
|
y1={from_node.y.to_string()}
|
|
x2={to_node.x.to_string()}
|
|
y2={to_node.y.to_string()}
|
|
class="transmission-line"
|
|
/>
|
|
</g>
|
|
}
|
|
} else {
|
|
html! {}
|
|
}
|
|
})
|
|
.collect::<Html>();
|
|
|
|
html! {
|
|
<div class="network-animation-overlay">
|
|
<svg
|
|
viewBox="0 0 783.086 400.649"
|
|
class="network-overlay-svg"
|
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;"
|
|
>
|
|
<defs>
|
|
// Minimal gradient for node glow
|
|
<@{"radialGradient"} id="nodeGlow" cx="50%" cy="50%" r="50%">
|
|
<stop offset="0%" style="stop-color: var(--primary-color, #007bff); stop-opacity: 0.3" />
|
|
<stop offset="100%" style="stop-color: var(--primary-color, #007bff); stop-opacity: 0" />
|
|
</@>
|
|
</defs>
|
|
|
|
<g class="server-nodes">
|
|
{ for server_pins }
|
|
</g>
|
|
|
|
<g class="transmissions">
|
|
{ transmissions }
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
|
|
if ctx.props().all_circles != old_props.all_circles {
|
|
self.server_nodes = Self::calculate_server_positions(&ctx.props().all_circles);
|
|
self.active_transmissions.clear();
|
|
return true;
|
|
}
|
|
false
|
|
}
|
|
}
|