chat_ui_components/components/
peer_list.rs1use dioxus::prelude::*;
2use chat_core::history::types::ConnectionStats;
3use crate::transport_utils::transport_name_to_info;
4
5#[component]
6pub fn PeerList(stats: Option<Box<ConnectionStats>>, sidebar_open: bool) -> Element {
7 let mut selected_peer = use_signal(|| None::<String>);
8
9 use_effect(move || {
10 if !sidebar_open {
11 selected_peer.set(None);
12 }
13 });
14
15 let (room_peers, connected_peers, peer_transports, peer_addresses) = match stats {
16 Some(ref s) => (
17 s.room_peers.clone(),
18 s.connected_peers.clone(),
19 s.peer_transports.clone(),
20 s.peer_addresses.clone(),
21 ),
22 None => (
23 vec![],
24 vec![],
25 std::collections::HashMap::new(),
26 std::collections::HashMap::new(),
27 ),
28 };
29
30 let mut on_peer_click = move |peer_id: String| {
31 if selected_peer() == Some(peer_id.clone()) {
32 selected_peer.set(None);
33 } else {
34 selected_peer.set(Some(peer_id));
35 }
36 };
37
38 let render_peer_item = move |peer_id: String, is_room_peer: bool| {
39 let status_class = if is_room_peer { "peer-status mesh" } else { "peer-status connected" };
40 let short_id = peer_id.chars().take(10).collect::<String>();
41 let popup_open = selected_peer() == Some(peer_id.clone());
42 let addrs = peer_addresses.get(&peer_id).cloned().unwrap_or_default();
43 let pts = peer_transports.get(&peer_id).cloned().unwrap_or_default();
44 let pid_clone = peer_id.clone();
45 rsx! {
46 div {
47 key: "{peer_id}",
48 class: "peer-item",
49 onclick: move |_| on_peer_click(pid_clone.clone()),
50 div {
51 class: "peer-item-row",
52 div {
53 class: "peer-info",
54 span {
55 class: status_class,
56 title: if is_room_peer { "In room mesh" } else { "Connected" },
57 "●"
58 }
59 span {
60 class: "peer-id",
61 title: "{peer_id}",
62 "{short_id}"
63 }
64 }
65 div {
66 class: "peer-transports",
67 for (idx, name) in pts.iter().enumerate() {
68 {
69 let info = transport_name_to_info(name);
70 rsx! {
71 span {
72 key: "pt-{idx}",
73 title: "{info.1}",
74 "{info.0}"
75 }
76 }
77 }
78 }
79 }
80 }
81 if popup_open {
82 div {
83 class: "peer-address-popup",
84 div { class: "popup-title", "Known Addresses" }
85 if addrs.is_empty() {
86 div { class: "popup-empty", "No known addresses" }
87 } else {
88 for addr in addrs {
89 div { class: "popup-addr", "{addr}" }
90 }
91 }
92 }
93 }
94 }
95 }
96 };
97
98 let render_section = move |title: &str, peers: &Vec<String>, is_room_section: bool| {
99 let title = title.to_string();
100 let peers = peers.clone();
101 rsx! {
102 div {
103 div { class: "peer-section-title", "{title}" }
104 if peers.is_empty() {
105 div { class: "peer-empty", "None yet" }
106 } else {
107 for peer_id in peers {
108 { render_peer_item(peer_id.clone(), is_room_section) }
109 }
110 }
111 }
112 }
113 };
114
115 let connected_only: Vec<String> = connected_peers
116 .into_iter()
117 .filter(|p| !room_peers.contains(p))
118 .collect();
119 let chat_section = render_section("Chat Peers", &room_peers, true);
120 let connected_section = render_section("Connected Peers", &connected_only, false);
121
122 rsx! {
123 {chat_section}
124 {connected_section}
125 }
126}