chat_ui_components/
chat_app.rs1use dioxus::prelude::*;
2use chat_core::coordinator::{ChatCoordinatorHandle, CoordinatorEvent};
3use chat_core::history::types::{ChatMessage, ConnectionStats};
4use crate::components::{ChatLayout, RoomPrompt, TabLocked};
5
6#[derive(Clone, Copy, PartialEq)]
8pub struct ChatAppSignals {
9 pub tab_lock_acquired: Signal<bool>,
10 pub room_name: Signal<Option<String>>,
11 pub messages: Signal<Vec<ChatMessage>>,
12 pub nickname: Signal<String>,
13 pub is_online: Signal<bool>,
14 pub stats: Signal<Option<Box<ConnectionStats>>>,
15 pub system_messages: Signal<Vec<String>>,
16 pub error_messages: Signal<Vec<String>>,
17 pub show_stats: Signal<bool>,
18 pub sidebar_open: Signal<bool>,
19 pub sidebar_collapsed: Signal<bool>,
20 pub coordinator_handle: Signal<Option<ChatCoordinatorHandle>>,
21}
22
23pub fn use_chat_app_signals() -> ChatAppSignals {
25 ChatAppSignals {
26 tab_lock_acquired: use_signal(|| true),
27 room_name: use_signal(|| None::<String>),
28 messages: use_signal(Vec::new),
29 nickname: use_signal(|| "anon".to_string()),
30 is_online: use_signal(|| false),
31 stats: use_signal(|| None::<Box<ConnectionStats>>),
32 system_messages: use_signal(Vec::<String>::new),
33 error_messages: use_signal(Vec::<String>::new),
34 show_stats: use_signal(|| false),
35 sidebar_open: use_signal(|| false),
36 sidebar_collapsed: use_signal(|| false),
37 coordinator_handle: use_signal(|| None::<ChatCoordinatorHandle>),
38 }
39}
40
41pub fn handle_chat_event(event: CoordinatorEvent, mut signals: ChatAppSignals) {
43 match event {
44 CoordinatorEvent::MessagesUpdated(msgs) => {
45 signals.messages.set(msgs);
46 }
47 CoordinatorEvent::PeerConnected { .. } => {}
48 CoordinatorEvent::PeerDisconnected { .. } => {}
49 CoordinatorEvent::StatsUpdated(s) => {
50 signals.stats.set(Some(s));
51 }
52 CoordinatorEvent::SystemMessage(msg) => {
53 let mut current = signals.system_messages.read().clone();
54 current.push(msg);
55 signals.system_messages.set(current);
56 }
57 CoordinatorEvent::ErrorMessage(msg) => {
58 let mut current = signals.error_messages.read().clone();
59 current.push(msg);
60 signals.error_messages.set(current);
61 }
62 CoordinatorEvent::RoomJoined { room_name: name } => {
63 signals.room_name.set(Some(name));
64 }
65 CoordinatorEvent::NicknameChanged { nickname: nick } => {
66 signals.nickname.set(nick);
67 }
68 CoordinatorEvent::IsOnline(online) => {
69 signals.is_online.set(online);
70 }
71 }
72}
73
74pub async fn run_chat_event_loop(
76 mut events: async_broadcast::Receiver<CoordinatorEvent>,
77 signals: ChatAppSignals,
78) {
79 while let Ok(event) = events.recv().await {
80 handle_chat_event(event, signals);
81 }
82}
83
84#[component]
87pub fn ChatApp(
88 signals: ChatAppSignals,
89 on_room_left: Option<EventHandler<()>>,
90) -> Element {
91 let ChatAppSignals {
92 tab_lock_acquired,
93 mut room_name,
94 messages,
95 nickname,
96 is_online,
97 stats,
98 system_messages,
99 error_messages,
100 mut show_stats,
101 mut sidebar_open,
102 mut sidebar_collapsed,
103 coordinator_handle,
104 } = signals;
105
106 rsx! {
107 if !tab_lock_acquired() {
108 TabLocked {}
109 } else if room_name().is_none() {
110 RoomPrompt {
111 on_join: move |name: String| {
112 if let Some(handle) = coordinator_handle.read().as_ref() {
113 let handle = handle.clone();
114 spawn(async move {
115 handle.join_room(name).await;
116 });
117 }
118 }
119 }
120 } else {
121 ChatLayout {
122 room_name: room_name().unwrap_or_default(),
123 nickname: nickname(),
124 messages: messages(),
125 is_online: is_online(),
126 stats: stats(),
127 system_messages: system_messages(),
128 error_messages: error_messages(),
129 show_stats: show_stats(),
130 sidebar_open: sidebar_open(),
131 sidebar_collapsed: sidebar_collapsed(),
132 on_send_message: move |content: String| {
133 if let Some(handle) = coordinator_handle.read().as_ref() {
134 let handle = handle.clone();
135 spawn(async move {
136 handle.send_message(content).await;
137 });
138 }
139 },
140 on_set_nickname: move |nick: String| {
141 if let Some(handle) = coordinator_handle.read().as_ref() {
142 let handle = handle.clone();
143 spawn(async move {
144 handle.set_nickname(nick).await;
145 });
146 }
147 },
148 on_dial_peer: move |addr: String| {
149 if let Some(handle) = coordinator_handle.read().as_ref() {
150 let handle = handle.clone();
151 spawn(async move {
152 handle.dial_peer(addr).await;
153 });
154 }
155 },
156 on_leave_room: move || {
157 if let Some(handle) = coordinator_handle.read().as_ref() {
158 let handle = handle.clone();
159 spawn(async move {
160 handle.leave_room().await;
161 });
162 }
163 room_name.set(None);
164 if let Some(cb) = on_room_left {
165 cb.call(());
166 }
167 },
168 on_toggle_stats: move || {
169 show_stats.set(!show_stats());
170 },
171 on_toggle_sidebar: move || {
172 sidebar_open.set(!sidebar_open());
173 sidebar_collapsed.set(!sidebar_collapsed());
174 },
175 on_close_sidebar: move || {
176 sidebar_open.set(false);
177 sidebar_collapsed.set(true);
178 },
179 }
180 }
181 }
182}