Skip to main content

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}