2021-08-24 10:23:50 +00:00
|
|
|
use crate::{
|
|
|
|
channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
|
2021-08-24 12:17:15 +00:00
|
|
|
editor::Editor,
|
2021-08-24 14:15:46 +00:00
|
|
|
util::ResultExt,
|
2021-08-24 10:23:50 +00:00
|
|
|
Settings,
|
|
|
|
};
|
2021-08-24 12:17:15 +00:00
|
|
|
use gpui::{
|
2021-08-24 15:29:14 +00:00
|
|
|
action, elements::*, keymap::Binding, Entity, ModelHandle, MutableAppContext, RenderContext,
|
|
|
|
Subscription, View, ViewContext, ViewHandle,
|
2021-08-24 12:17:15 +00:00
|
|
|
};
|
2021-08-24 10:23:50 +00:00
|
|
|
use postage::watch;
|
2021-08-25 22:22:14 +00:00
|
|
|
use time::{OffsetDateTime, UtcOffset};
|
2021-08-20 21:28:45 +00:00
|
|
|
|
|
|
|
pub struct ChatPanel {
|
2021-08-23 22:02:42 +00:00
|
|
|
channel_list: ModelHandle<ChannelList>,
|
2021-08-24 00:21:06 +00:00
|
|
|
active_channel: Option<(ModelHandle<Channel>, Subscription)>,
|
2021-08-26 14:36:56 +00:00
|
|
|
message_list: ListState,
|
2021-08-24 12:17:15 +00:00
|
|
|
input_editor: ViewHandle<Editor>,
|
2021-08-24 10:23:50 +00:00
|
|
|
settings: watch::Receiver<Settings>,
|
2021-08-20 21:28:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub enum Event {}
|
|
|
|
|
2021-08-24 14:15:46 +00:00
|
|
|
action!(Send);
|
|
|
|
|
|
|
|
pub fn init(cx: &mut MutableAppContext) {
|
|
|
|
cx.add_action(ChatPanel::send);
|
2021-08-24 15:29:14 +00:00
|
|
|
|
|
|
|
cx.add_bindings(vec![Binding::new("enter", Send, Some("ChatPanel"))]);
|
2021-08-24 14:15:46 +00:00
|
|
|
}
|
|
|
|
|
2021-08-23 15:29:46 +00:00
|
|
|
impl ChatPanel {
|
2021-08-24 10:23:50 +00:00
|
|
|
pub fn new(
|
|
|
|
channel_list: ModelHandle<ChannelList>,
|
|
|
|
settings: watch::Receiver<Settings>,
|
|
|
|
cx: &mut ViewContext<Self>,
|
|
|
|
) -> Self {
|
2021-08-24 12:17:15 +00:00
|
|
|
let input_editor = cx.add_view(|cx| Editor::auto_height(settings.clone(), cx));
|
2021-08-23 22:02:42 +00:00
|
|
|
let mut this = Self {
|
|
|
|
channel_list,
|
2021-08-26 14:36:56 +00:00
|
|
|
active_channel: Default::default(),
|
|
|
|
message_list: ListState::new(0, Orientation::Bottom),
|
2021-08-24 12:17:15 +00:00
|
|
|
input_editor,
|
2021-08-24 10:23:50 +00:00
|
|
|
settings,
|
2021-08-23 22:02:42 +00:00
|
|
|
};
|
2021-08-24 00:21:06 +00:00
|
|
|
|
2021-08-24 15:29:14 +00:00
|
|
|
this.init_active_channel(cx);
|
2021-08-24 00:21:06 +00:00
|
|
|
cx.observe(&this.channel_list, |this, _, cx| {
|
2021-08-24 15:29:14 +00:00
|
|
|
this.init_active_channel(cx);
|
2021-08-24 00:21:06 +00:00
|
|
|
})
|
|
|
|
.detach();
|
|
|
|
|
|
|
|
this
|
|
|
|
}
|
|
|
|
|
2021-08-24 15:29:14 +00:00
|
|
|
fn init_active_channel(&mut self, cx: &mut ViewContext<Self>) {
|
|
|
|
if self.active_channel.is_none() {
|
|
|
|
let channel = self.channel_list.update(cx, |list, cx| {
|
|
|
|
if let Some(channel_id) = list
|
|
|
|
.available_channels()
|
|
|
|
.and_then(|channels| channels.first())
|
|
|
|
.map(|details| details.id)
|
|
|
|
{
|
|
|
|
return list.get_channel(channel_id, cx);
|
|
|
|
}
|
|
|
|
None
|
|
|
|
});
|
|
|
|
if let Some(channel) = channel {
|
|
|
|
self.set_active_channel(channel, cx);
|
2021-08-24 00:21:06 +00:00
|
|
|
}
|
2021-08-24 15:29:14 +00:00
|
|
|
} else if self.channel_list.read(cx).available_channels().is_none() {
|
2021-08-24 00:21:06 +00:00
|
|
|
self.active_channel = None;
|
2021-08-23 15:29:46 +00:00
|
|
|
}
|
2021-08-23 22:02:42 +00:00
|
|
|
}
|
|
|
|
|
2021-08-24 15:29:14 +00:00
|
|
|
fn set_active_channel(&mut self, channel: ModelHandle<Channel>, cx: &mut ViewContext<Self>) {
|
|
|
|
if self.active_channel.as_ref().map(|e| &e.0) != Some(&channel) {
|
|
|
|
let subscription = cx.subscribe(&channel, Self::channel_did_change);
|
2021-08-26 14:36:56 +00:00
|
|
|
self.message_list =
|
|
|
|
ListState::new(channel.read(cx).message_count(), Orientation::Bottom);
|
2021-08-24 15:29:14 +00:00
|
|
|
self.active_channel = Some((channel, subscription));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-24 10:23:50 +00:00
|
|
|
fn channel_did_change(
|
|
|
|
&mut self,
|
2021-08-26 14:36:56 +00:00
|
|
|
_: ModelHandle<Channel>,
|
2021-08-24 10:23:50 +00:00
|
|
|
event: &ChannelEvent,
|
|
|
|
cx: &mut ViewContext<Self>,
|
|
|
|
) {
|
|
|
|
match event {
|
2021-08-24 20:12:02 +00:00
|
|
|
ChannelEvent::Message {
|
|
|
|
old_range,
|
|
|
|
new_count,
|
|
|
|
} => {
|
2021-08-26 14:36:56 +00:00
|
|
|
self.message_list.splice(old_range.clone(), *new_count);
|
2021-08-24 10:23:50 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-24 00:21:06 +00:00
|
|
|
cx.notify();
|
2021-08-23 15:29:46 +00:00
|
|
|
}
|
2021-08-24 10:23:50 +00:00
|
|
|
|
2021-08-26 14:36:56 +00:00
|
|
|
fn render_active_channel_messages(&self, cx: &RenderContext<Self>) -> ElementBox {
|
|
|
|
let messages = if let Some((channel, _)) = self.active_channel.as_ref() {
|
|
|
|
let channel = channel.read(cx);
|
|
|
|
let now = OffsetDateTime::now_utc();
|
|
|
|
List::new(self.message_list.clone(), cx, |range| {
|
|
|
|
channel
|
|
|
|
.messages_in_range(range)
|
|
|
|
.map(|message| self.render_message(message, now))
|
|
|
|
})
|
|
|
|
.boxed()
|
|
|
|
} else {
|
|
|
|
Empty::new().boxed()
|
|
|
|
};
|
|
|
|
|
|
|
|
Expanded::new(1., messages).boxed()
|
2021-08-24 10:23:50 +00:00
|
|
|
}
|
|
|
|
|
2021-08-25 22:22:14 +00:00
|
|
|
fn render_message(&self, message: &ChannelMessage, now: OffsetDateTime) -> ElementBox {
|
2021-08-24 10:23:50 +00:00
|
|
|
let settings = self.settings.borrow();
|
2021-08-25 22:22:14 +00:00
|
|
|
let theme = &settings.theme.chat_panel.message;
|
|
|
|
Flex::column()
|
|
|
|
.with_child(
|
|
|
|
Flex::row()
|
|
|
|
.with_child(
|
|
|
|
Container::new(
|
|
|
|
Label::new(
|
|
|
|
message.sender.github_login.clone(),
|
2021-08-26 22:06:00 +00:00
|
|
|
theme.sender.label.clone(),
|
2021-08-25 22:22:14 +00:00
|
|
|
)
|
|
|
|
.boxed(),
|
|
|
|
)
|
|
|
|
.with_style(&theme.sender.container)
|
|
|
|
.boxed(),
|
|
|
|
)
|
|
|
|
.with_child(
|
|
|
|
Container::new(
|
|
|
|
Label::new(
|
|
|
|
format_timestamp(message.timestamp, now),
|
2021-08-26 22:06:00 +00:00
|
|
|
theme.timestamp.label.clone(),
|
2021-08-25 22:22:14 +00:00
|
|
|
)
|
|
|
|
.boxed(),
|
|
|
|
)
|
|
|
|
.with_style(&theme.timestamp.container)
|
|
|
|
.boxed(),
|
|
|
|
)
|
|
|
|
.boxed(),
|
|
|
|
)
|
2021-08-26 22:06:00 +00:00
|
|
|
.with_child(Text::new(message.body.clone(), theme.body.clone()).boxed())
|
2021-08-25 22:22:14 +00:00
|
|
|
.boxed()
|
2021-08-24 10:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn render_input_box(&self) -> ElementBox {
|
2021-08-24 12:17:15 +00:00
|
|
|
ConstrainedBox::new(ChildView::new(self.input_editor.id()).boxed())
|
|
|
|
.with_max_height(100.)
|
|
|
|
.boxed()
|
2021-08-24 10:23:50 +00:00
|
|
|
}
|
2021-08-24 14:15:46 +00:00
|
|
|
|
|
|
|
fn send(&mut self, _: &Send, cx: &mut ViewContext<Self>) {
|
|
|
|
if let Some((channel, _)) = self.active_channel.as_ref() {
|
|
|
|
let body = self.input_editor.update(cx, |editor, cx| {
|
|
|
|
let body = editor.text(cx);
|
|
|
|
editor.clear(cx);
|
|
|
|
body
|
|
|
|
});
|
|
|
|
|
|
|
|
channel
|
|
|
|
.update(cx, |channel, cx| channel.send_message(body, cx))
|
|
|
|
.log_err();
|
|
|
|
}
|
|
|
|
}
|
2021-08-23 15:29:46 +00:00
|
|
|
}
|
|
|
|
|
2021-08-20 21:28:45 +00:00
|
|
|
impl Entity for ChatPanel {
|
|
|
|
type Event = Event;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl View for ChatPanel {
|
|
|
|
fn ui_name() -> &'static str {
|
|
|
|
"ChatPanel"
|
|
|
|
}
|
|
|
|
|
2021-08-26 14:36:56 +00:00
|
|
|
fn render(&self, cx: &RenderContext<Self>) -> ElementBox {
|
2021-08-25 22:22:14 +00:00
|
|
|
let theme = &self.settings.borrow().theme;
|
|
|
|
Container::new(
|
|
|
|
Flex::column()
|
2021-08-26 14:36:56 +00:00
|
|
|
.with_child(self.render_active_channel_messages(cx))
|
2021-08-25 22:22:14 +00:00
|
|
|
.with_child(self.render_input_box())
|
|
|
|
.boxed(),
|
|
|
|
)
|
|
|
|
.with_style(&theme.chat_panel.container)
|
|
|
|
.boxed()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn format_timestamp(mut timestamp: OffsetDateTime, mut now: OffsetDateTime) -> String {
|
|
|
|
let local_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
|
|
|
|
timestamp = timestamp.to_offset(local_offset);
|
|
|
|
now = now.to_offset(local_offset);
|
|
|
|
|
|
|
|
let today = now.date();
|
|
|
|
let date = timestamp.date();
|
|
|
|
let mut hour = timestamp.hour();
|
|
|
|
let mut part = "am";
|
|
|
|
if hour > 12 {
|
|
|
|
hour -= 12;
|
|
|
|
part = "pm";
|
|
|
|
}
|
|
|
|
if date == today {
|
|
|
|
format!("{}:{}{}", hour, timestamp.minute(), part)
|
|
|
|
} else if date.next_day() == Some(today) {
|
|
|
|
format!("yesterday at {}:{}{}", hour, timestamp.minute(), part)
|
|
|
|
} else {
|
|
|
|
format!("{}/{}/{}", date.month(), date.day(), date.year())
|
2021-08-20 21:28:45 +00:00
|
|
|
}
|
|
|
|
}
|