chat_ui_components/components/
peer_list.rs

1use 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}