egui_charts/services/chats.rs
1//! Chat rooms the host connects the user to.
2
3use chrono::{DateTime, Utc};
4
5use super::Events;
6use crate::data::{FeedError, MaybeSend, Reply};
7
8/// The host's chat rooms, for the chats panel. The host keeps the account, the rooms and their
9/// messages, and moderates; the panel shows what the service pushes and sends what the user
10/// writes through [`send`](Self::send).
11///
12/// A message the user sends appears once the host pushes it back. Choosing recipients, creating
13/// rooms and changing their members are optional operations: the host turns each on in
14/// [`capabilities`](Self::capabilities), keeps the accounts and decides whether the signed-in
15/// user may do it, and answers a refusal with an error that the panel shows. Methods are called
16/// on the UI thread and must return at once; answer and push from any thread, runtime or browser
17/// task.
18///
19/// ```
20/// use std::sync::Mutex;
21///
22/// use egui_charts::data::Reply;
23/// use egui_charts::services::{ChatEvent, ChatMessage, Chats, Events};
24///
25/// /// One room that echoes what the user writes. A real host sends it to its chat server.
26/// #[derive(Default)]
27/// struct Echo(Mutex<Option<Events<ChatEvent>>>);
28///
29/// impl Chats for Echo {
30/// fn watch(&self, events: Events<ChatEvent>) {
31/// events.push(ChatEvent::Room("General".into()));
32/// *self.0.lock().unwrap() = Some(events);
33/// }
34///
35/// fn send(&self, room: String, text: String, reply: Reply<()>) {
36/// if let Some(events) = &*self.0.lock().unwrap() {
37/// let message = ChatMessage::new(room, "You", chrono::Utc::now(), text);
38/// events.push(ChatEvent::Message(message));
39/// }
40/// reply.send(Ok(()));
41/// }
42/// }
43/// ```
44pub trait Chats: MaybeSend + 'static {
45 /// Pushes the user's rooms and their recent messages, oldest first, then every new room and
46 /// message until `events` is inactive. Called when the panel starts and again after a lagged
47 /// stream, each time with a new stream that starts from no rooms.
48 ///
49 /// The stream takes at most 10,000 pushes between two frames: push at most that many recent
50 /// messages at once, or the stream lags on every call and nothing shows.
51 fn watch(&self, events: Events<ChatEvent>);
52
53 /// Posts `text` to `room`. Answer `Ok` once it is posted, and push the message.
54 fn send(&self, room: String, text: String, reply: Reply<()>);
55
56 /// The optional operations below this host supports. The default supports none, and the panel
57 /// then offers only to read and post. Read each frame, so it may change with the account.
58 fn capabilities(&self) -> ChatCapabilities {
59 ChatCapabilities::default()
60 }
61
62 /// The users whose name matches `query`, for choosing recipients and members. The default
63 /// answers [`FeedError::NotSupported`].
64 fn find_users(&self, query: String, reply: Reply<Vec<ChatUser>>) {
65 let _ = query;
66 reply.send(Err(FeedError::NotSupported));
67 }
68
69 /// Creates `room` and answers its identifier once it exists. Push the room, as
70 /// [`ChatEvent::Room`] or [`ChatEvent::Members`], for the panel to show it. The default
71 /// answers [`FeedError::NotSupported`].
72 fn create_room(&self, room: NewChatRoom, reply: Reply<String>) {
73 let _ = room;
74 reply.send(Err(FeedError::NotSupported));
75 }
76
77 /// Adds the user with identifier `user` to `room`. Answer `Ok` once it is done, and push the
78 /// new [`ChatEvent::Members`]. The default answers [`FeedError::NotSupported`].
79 fn add_member(&self, room: String, user: String, reply: Reply<()>) {
80 let _ = (room, user);
81 reply.send(Err(FeedError::NotSupported));
82 }
83
84 /// Removes the user with identifier `user` from `room`. Answer `Ok` once it is done, and push
85 /// the new [`ChatEvent::Members`]. The default answers [`FeedError::NotSupported`].
86 fn remove_member(&self, room: String, user: String, reply: Reply<()>) {
87 let _ = (room, user);
88 reply.send(Err(FeedError::NotSupported));
89 }
90}
91
92/// The optional operations of a [`Chats`] host.
93#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
94#[non_exhaustive]
95pub struct ChatCapabilities {
96 /// The user can start a direct message with one other user.
97 pub direct_messages: bool,
98 /// The user can create private and group rooms.
99 pub create_rooms: bool,
100 /// The user can add and remove the members of a room that pushes its members.
101 pub manage_members: bool,
102}
103
104/// A user, as the host identifies and names them.
105#[derive(Clone, Debug, PartialEq, Eq)]
106#[non_exhaustive]
107pub struct ChatUser {
108 /// Stable host identifier of the user.
109 pub id: String,
110 /// The name shown for the user.
111 pub name: String,
112}
113
114impl ChatUser {
115 /// The user `name` with host identifier `id`.
116 pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
117 Self {
118 id: id.into(),
119 name: name.into(),
120 }
121 }
122}
123
124/// Who may join a room the user creates.
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126#[non_exhaustive]
127pub enum NewChatRoomKind {
128 /// A room of the user and one other user.
129 Direct,
130 /// A room only the invited users can join.
131 Private,
132 /// A named room of the invited users that others may be allowed to join.
133 Group,
134}
135
136/// A room the user asks the host to create.
137#[derive(Clone, Debug, PartialEq, Eq)]
138#[non_exhaustive]
139pub struct NewChatRoom {
140 /// Who may join.
141 pub kind: NewChatRoomKind,
142 /// The room's name; empty for a direct message.
143 pub name: String,
144 /// Host identifiers of the users to invite.
145 pub members: Vec<String>,
146}
147
148/// A change to the user's rooms, pushed through the stream [`Chats::watch`] received.
149#[derive(Clone, Debug, PartialEq)]
150#[non_exhaustive]
151pub enum ChatEvent {
152 /// The user can read and post in this room.
153 Room(String),
154 /// A message in a room, which also adds the room.
155 Message(ChatMessage),
156 /// The members of a room, replacing those pushed before, which also adds the room.
157 Members {
158 /// Host identifier of the room.
159 room: String,
160 /// Everyone in the room.
161 members: Vec<ChatUser>,
162 },
163 /// Removes every room, member and message, so a host that reconnects can push them again.
164 Clear,
165}
166
167/// One message of a room.
168#[derive(Clone, Debug, PartialEq)]
169#[non_exhaustive]
170pub struct ChatMessage {
171 /// Host identifier of the chat room.
172 pub room: String,
173 /// The author's display name.
174 pub author: String,
175 /// UTC timestamp supplied by the host.
176 pub time: DateTime<Utc>,
177 /// Message text.
178 pub text: String,
179}
180
181impl ChatMessage {
182 /// The message `text` of `author` in `room`, posted at `time`.
183 pub fn new(
184 room: impl Into<String>,
185 author: impl Into<String>,
186 time: DateTime<Utc>,
187 text: impl Into<String>,
188 ) -> Self {
189 Self {
190 room: room.into(),
191 author: author.into(),
192 time,
193 text: text.into(),
194 }
195 }
196}