use crate::{ channel::{Channel, ChannelEvent, ChannelList, ChannelMessage}, editor::Editor, util::ResultExt, Settings, }; use gpui::{ action, elements::*, keymap::Binding, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, }; use postage::watch; pub struct ChatPanel { channel_list: ModelHandle, active_channel: Option<(ModelHandle, Subscription)>, messages: ListState, input_editor: ViewHandle, settings: watch::Receiver, } pub enum Event {} action!(Send); pub fn init(cx: &mut MutableAppContext) { cx.add_action(ChatPanel::send); cx.add_bindings(vec![Binding::new("enter", Send, Some("ChatPanel"))]); } impl ChatPanel { pub fn new( channel_list: ModelHandle, settings: watch::Receiver, cx: &mut ViewContext, ) -> Self { let input_editor = cx.add_view(|cx| Editor::auto_height(settings.clone(), cx)); let mut this = Self { channel_list, active_channel: None, messages: ListState::new(Vec::new()), input_editor, settings, }; this.init_active_channel(cx); cx.observe(&this.channel_list, |this, _, cx| { this.init_active_channel(cx); }) .detach(); this } fn init_active_channel(&mut self, cx: &mut ViewContext) { 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); } } else if self.channel_list.read(cx).available_channels().is_none() { self.active_channel = None; } } fn set_active_channel(&mut self, channel: ModelHandle, cx: &mut ViewContext) { if self.active_channel.as_ref().map(|e| &e.0) != Some(&channel) { let subscription = cx.subscribe(&channel, Self::channel_did_change); self.messages = ListState::new( channel .read(cx) .messages() .cursor::<(), ()>() .map(|m| self.render_message(m)) .collect(), ); self.active_channel = Some((channel, subscription)); } } fn channel_did_change( &mut self, channel: ModelHandle, event: &ChannelEvent, cx: &mut ViewContext, ) { match event { ChannelEvent::Message { old_range, new_count, } => { self.messages.splice( old_range.clone(), channel .read(cx) .messages_in_range(old_range.start..(old_range.start + new_count)) .map(|message| self.render_message(message)), ); } } cx.notify(); } fn render_active_channel_messages(&self) -> ElementBox { Expanded::new(1., List::new(self.messages.clone()).boxed()).boxed() } fn render_message(&self, message: &ChannelMessage) -> ElementBox { let settings = self.settings.borrow(); Flex::column() .with_child( Label::new( message.body.clone(), settings.ui_font_family, settings.ui_font_size, ) .boxed(), ) .boxed() } fn render_input_box(&self) -> ElementBox { ConstrainedBox::new(ChildView::new(self.input_editor.id()).boxed()) .with_max_height(100.) .boxed() } fn send(&mut self, _: &Send, cx: &mut ViewContext) { 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(); } } } impl Entity for ChatPanel { type Event = Event; } impl View for ChatPanel { fn ui_name() -> &'static str { "ChatPanel" } fn render(&self, _: &RenderContext) -> ElementBox { Flex::column() .with_child(self.render_active_channel_messages()) .with_child(self.render_input_box()) .boxed() } }