@@ -196,10 +196,15 @@ impl ChannelStore {
)
}
+ /// Asynchronously open a given resource associated with a channel.
+ ///
+ /// Make sure that the resource is only opened once, even if this method
+ /// is called multiple times with the same channel id while the first task
+ /// is still running.
fn open_channel_resource<T: Entity, F, Fut>(
&mut self,
channel_id: ChannelId,
- map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
+ get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
load: F,
cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<T>>>
@@ -207,21 +212,20 @@ impl ChannelStore {
F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
Fut: Future<Output = Result<ModelHandle<T>>>,
{
- // Make sure that a given channel resource is only opened once per
- // app instance, even if this method is called multiple times
- // with the same channel id while the first task is still running.
let task = loop {
- match map(self).entry(channel_id) {
+ match get_map(self).entry(channel_id) {
hash_map::Entry::Occupied(e) => match e.get() {
- OpenedModelHandle::Open(buffer) => {
- if let Some(buffer) = buffer.upgrade(cx) {
- break Task::ready(Ok(buffer)).shared();
+ OpenedModelHandle::Open(model) => {
+ if let Some(model) = model.upgrade(cx) {
+ break Task::ready(Ok(model)).shared();
} else {
- map(self).remove(&channel_id);
+ get_map(self).remove(&channel_id);
continue;
}
}
- OpenedModelHandle::Loading(task) => break task.clone(),
+ OpenedModelHandle::Loading(task) => {
+ break task.clone();
+ }
},
hash_map::Entry::Vacant(e) => {
let task = cx
@@ -235,25 +239,21 @@ impl ChannelStore {
load(channel, cx).await.map_err(Arc::new)
})
.shared();
+
e.insert(OpenedModelHandle::Loading(task.clone()));
cx.spawn({
let task = task.clone();
|this, mut cx| async move {
let result = task.await;
- this.update(&mut cx, |this, cx| match result {
- Ok(buffer) => {
- cx.observe_release(&buffer, move |this, _, _| {
- this.opened_buffers.remove(&channel_id);
- })
- .detach();
- map(this).insert(
+ this.update(&mut cx, |this, _| match result {
+ Ok(model) => {
+ get_map(this).insert(
channel_id,
- OpenedModelHandle::Open(buffer.downgrade()),
+ OpenedModelHandle::Open(model.downgrade()),
);
}
- Err(error) => {
- log::error!("failed to open channel buffer {error:?}");
- map(this).remove(&channel_id);
+ Err(_) => {
+ get_map(this).remove(&channel_id);
}
});
}
@@ -33,7 +33,7 @@ const CHAT_PANEL_KEY: &'static str = "ChatPanel";
pub struct ChatPanel {
client: Arc<Client>,
channel_store: ModelHandle<ChannelStore>,
- active_channel: Option<(ModelHandle<ChannelChat>, Subscription)>,
+ active_chat: Option<(ModelHandle<ChannelChat>, Subscription)>,
message_list: ListState<ChatPanel>,
input_editor: ViewHandle<Editor>,
channel_select: ViewHandle<Select>,
@@ -105,7 +105,7 @@ impl ChatPanel {
let mut message_list =
ListState::<Self>::new(0, Orientation::Bottom, 1000., move |this, ix, cx| {
- let message = this.active_channel.as_ref().unwrap().0.read(cx).message(ix);
+ let message = this.active_chat.as_ref().unwrap().0.read(cx).message(ix);
this.render_message(message, cx)
});
message_list.set_scroll_handler(|visible_range, this, cx| {
@@ -119,7 +119,7 @@ impl ChatPanel {
fs,
client,
channel_store,
- active_channel: Default::default(),
+ active_chat: Default::default(),
pending_serialization: Task::ready(None),
message_list,
input_editor,
@@ -151,6 +151,7 @@ impl ChatPanel {
cx.observe(&this.channel_select, |this, channel_select, cx| {
let selected_ix = channel_select.read(cx).selected_index();
+
let selected_channel_id = this
.channel_store
.read(cx)
@@ -216,14 +217,14 @@ impl ChatPanel {
fn init_active_channel(&mut self, cx: &mut ViewContext<Self>) {
let channel_count = self.channel_store.read(cx).channel_count();
self.message_list.reset(0);
- self.active_channel = None;
+ self.active_chat = None;
self.channel_select.update(cx, |select, cx| {
select.set_item_count(channel_count, cx);
});
}
- fn set_active_channel(&mut self, chat: ModelHandle<ChannelChat>, cx: &mut ViewContext<Self>) {
- if self.active_channel.as_ref().map(|e| &e.0) != Some(&chat) {
+ fn set_active_chat(&mut self, chat: ModelHandle<ChannelChat>, cx: &mut ViewContext<Self>) {
+ if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
let id = chat.read(cx).channel().id;
{
let chat = chat.read(cx);
@@ -234,7 +235,7 @@ impl ChatPanel {
});
}
let subscription = cx.subscribe(&chat, Self::channel_did_change);
- self.active_channel = Some((chat, subscription));
+ self.active_chat = Some((chat, subscription));
self.channel_select.update(cx, |select, cx| {
if let Some(ix) = self.channel_store.read(cx).index_of_channel(id) {
select.set_selected_index(ix, cx);
@@ -275,7 +276,7 @@ impl ChatPanel {
}
fn render_active_channel_messages(&self) -> AnyElement<Self> {
- let messages = if self.active_channel.is_some() {
+ let messages = if self.active_chat.is_some() {
List::new(self.message_list.clone()).into_any()
} else {
Empty::new().into_any()
@@ -396,15 +397,15 @@ impl ChatPanel {
}
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
- if let Some((channel, _)) = self.active_channel.as_ref() {
+ if let Some((chat, _)) = self.active_chat.as_ref() {
let body = self.input_editor.update(cx, |editor, cx| {
let body = editor.text(cx);
editor.clear(cx);
body
});
- if let Some(task) = channel
- .update(cx, |channel, cx| channel.send_message(body, cx))
+ if let Some(task) = chat
+ .update(cx, |chat, cx| chat.send_message(body, cx))
.log_err()
{
task.detach();
@@ -413,8 +414,8 @@ impl ChatPanel {
}
fn load_more_messages(&mut self, _: &LoadMoreMessages, cx: &mut ViewContext<Self>) {
- if let Some((channel, _)) = self.active_channel.as_ref() {
- channel.update(cx, |channel, cx| {
+ if let Some((chat, _)) = self.active_chat.as_ref() {
+ chat.update(cx, |channel, cx| {
channel.load_more_messages(cx);
})
}
@@ -425,13 +426,19 @@ impl ChatPanel {
selected_channel_id: u64,
cx: &mut ViewContext<ChatPanel>,
) -> Task<Result<()>> {
+ if let Some((chat, _)) = &self.active_chat {
+ if chat.read(cx).channel().id == selected_channel_id {
+ return Task::ready(Ok(()));
+ }
+ }
+
let open_chat = self.channel_store.update(cx, |store, cx| {
store.open_channel_chat(selected_channel_id, cx)
});
cx.spawn(|this, mut cx| async move {
let chat = open_chat.await?;
this.update(&mut cx, |this, cx| {
- this.set_active_channel(chat, cx);
+ this.set_active_chat(chat, cx);
})
})
}
@@ -80,7 +80,7 @@ struct RenameChannel {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct OpenChannelBuffer {
+struct OpenChannelNotes {
channel_id: u64,
}
@@ -104,7 +104,7 @@ impl_actions!(
ManageMembers,
RenameChannel,
ToggleCollapse,
- OpenChannelBuffer
+ OpenChannelNotes
]
);
@@ -130,7 +130,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(CollabPanel::toggle_channel_collapsed);
cx.add_action(CollabPanel::collapse_selected_channel);
cx.add_action(CollabPanel::expand_selected_channel);
- cx.add_action(CollabPanel::open_channel_buffer);
+ cx.add_action(CollabPanel::open_channel_notes);
}
#[derive(Debug)]
@@ -226,10 +226,6 @@ enum ListEntry {
channel: Arc<Channel>,
depth: usize,
},
- ChannelCall {
- channel: Arc<Channel>,
- depth: usize,
- },
ChannelNotes {
channel_id: ChannelId,
},
@@ -369,13 +365,6 @@ impl CollabPanel {
return channel_row;
}
}
- ListEntry::ChannelCall { channel, depth } => this.render_channel_call(
- &*channel,
- *depth,
- &theme.collab_panel,
- is_selected,
- cx,
- ),
ListEntry::ChannelNotes { channel_id } => this.render_channel_notes(
*channel_id,
&theme.collab_panel,
@@ -751,12 +740,6 @@ impl CollabPanel {
channel: channel.clone(),
depth,
});
- if !channel_store.channel_participants(channel.id).is_empty() {
- self.entries.push(ListEntry::ChannelCall {
- channel: channel.clone(),
- depth,
- });
- }
}
}
}
@@ -1566,10 +1549,23 @@ impl CollabPanel {
let disclosed =
has_children.then(|| !self.collapsed_channels.binary_search(&channel_id).is_ok());
+ let is_active = iife!({
+ let call_channel = ActiveCall::global(cx)
+ .read(cx)
+ .room()?
+ .read(cx)
+ .channel_id()?;
+ Some(call_channel == channel_id)
+ })
+ .unwrap_or(false);
+
+ const FACEPILE_LIMIT: usize = 3;
+
enum ChannelCall {}
- enum ChannelNotes {}
MouseEventHandler::new::<Channel, _>(channel.id as usize, cx, |state, cx| {
+ let row_hovered = state.hovered();
+
Flex::<Self>::row()
.with_child(
Svg::new("icons/hash.svg")
@@ -1588,29 +1584,49 @@ impl CollabPanel {
.flex(1., true),
)
.with_child(
- MouseEventHandler::new::<ChannelCall, _>(channel_id as usize, cx, |_, _| {
- Svg::new("icons/radix/speaker-loud.svg")
- .with_color(theme.channel_hash.color)
- .constrained()
- .with_width(theme.channel_hash.width)
- .aligned()
- .right()
- })
- .on_click(MouseButton::Left, move |_, this, cx| {
- this.join_channel_call(channel_id, cx)
- }),
- )
- .with_child(
- MouseEventHandler::new::<ChannelNotes, _>(channel_id as usize, cx, |_, _| {
- Svg::new("icons/radix/file.svg")
- .with_color(theme.channel_hash.color)
- .constrained()
- .with_width(theme.channel_hash.width)
- .aligned()
- .right()
- })
+ MouseEventHandler::new::<ChannelCall, _>(
+ channel.id as usize,
+ cx,
+ move |_, cx| {
+ let participants =
+ self.channel_store.read(cx).channel_participants(channel_id);
+ if !participants.is_empty() {
+ let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
+
+ FacePile::new(theme.face_overlap)
+ .with_children(
+ participants
+ .iter()
+ .filter_map(|user| {
+ Some(
+ Image::from_data(user.avatar.clone()?)
+ .with_style(theme.channel_avatar),
+ )
+ })
+ .take(FACEPILE_LIMIT),
+ )
+ .with_children((extra_count > 0).then(|| {
+ Label::new(
+ format!("+{}", extra_count),
+ theme.extra_participant_label.text.clone(),
+ )
+ .contained()
+ .with_style(theme.extra_participant_label.container)
+ }))
+ .into_any()
+ } else if row_hovered {
+ Svg::new("icons/radix/speaker-loud.svg")
+ .with_color(theme.channel_hash.color)
+ .constrained()
+ .with_width(theme.channel_hash.width)
+ .into_any()
+ } else {
+ Empty::new().into_any()
+ }
+ },
+ )
.on_click(MouseButton::Left, move |_, this, cx| {
- this.open_channel_buffer(&OpenChannelBuffer { channel_id }, cx);
+ this.join_channel_call(channel_id, cx);
}),
)
.align_children_center()
@@ -1622,7 +1638,7 @@ impl CollabPanel {
.constrained()
.with_height(theme.row_height)
.contained()
- .with_style(*theme.channel_row.style_for(is_selected, state))
+ .with_style(*theme.channel_row.style_for(is_selected || is_active, state))
.with_padding_left(
theme.channel_row.default_style().padding.left
+ theme.channel_indent * depth as f32,
@@ -1638,94 +1654,6 @@ impl CollabPanel {
.into_any()
}
- fn render_channel_call(
- &self,
- channel: &Channel,
- depth: usize,
- theme: &theme::CollabPanel,
- is_selected: bool,
- cx: &mut ViewContext<Self>,
- ) -> AnyElement<Self> {
- let channel_id = channel.id;
-
- let is_active = iife!({
- let call_channel = ActiveCall::global(cx)
- .read(cx)
- .room()?
- .read(cx)
- .channel_id()?;
- Some(call_channel == channel_id)
- })
- .unwrap_or(false);
-
- const FACEPILE_LIMIT: usize = 5;
-
- enum ChannelCall {}
-
- let host_avatar_width = theme
- .contact_avatar
- .width
- .or(theme.contact_avatar.height)
- .unwrap_or(0.);
-
- MouseEventHandler::new::<ChannelCall, _>(channel.id as usize, cx, |state, cx| {
- let participants = self.channel_store.read(cx).channel_participants(channel_id);
- let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
- let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
- let row = theme.project_row.in_state(is_selected).style_for(state);
-
- Flex::<Self>::row()
- .with_child(render_tree_branch(
- tree_branch,
- &row.name.text,
- true,
- vec2f(host_avatar_width, theme.row_height),
- cx.font_cache(),
- ))
- .with_child(
- FacePile::new(theme.face_overlap)
- .with_children(
- participants
- .iter()
- .filter_map(|user| {
- Some(
- Image::from_data(user.avatar.clone()?)
- .with_style(theme.channel_avatar),
- )
- })
- .take(FACEPILE_LIMIT),
- )
- .with_children((extra_count > 0).then(|| {
- Label::new(
- format!("+{}", extra_count),
- theme.extra_participant_label.text.clone(),
- )
- .contained()
- .with_style(theme.extra_participant_label.container)
- })),
- )
- .align_children_center()
- .constrained()
- .with_height(theme.row_height)
- .aligned()
- .left()
- .contained()
- .with_style(*theme.channel_row.style_for(is_selected || is_active, state))
- .with_padding_left(
- theme.channel_row.default_style().padding.left
- + theme.channel_indent * (depth + 1) as f32,
- )
- })
- .on_click(MouseButton::Left, move |_, this, cx| {
- this.join_channel_call(channel_id, cx);
- })
- .on_click(MouseButton::Right, move |e, this, cx| {
- this.deploy_channel_context_menu(Some(e.position), channel_id, cx);
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .into_any()
- }
-
fn render_channel_notes(
&self,
channel_id: ChannelId,
@@ -1775,7 +1703,7 @@ impl CollabPanel {
.with_padding_left(theme.channel_row.default_style().padding.left)
})
.on_click(MouseButton::Left, move |_, this, cx| {
- this.open_channel_buffer(&OpenChannelBuffer { channel_id }, cx);
+ this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
})
.with_cursor_style(CursorStyle::PointingHand)
.into_any()
@@ -1987,7 +1915,7 @@ impl CollabPanel {
let mut items = vec![
ContextMenuItem::action(expand_action_name, ToggleCollapse { channel_id }),
- ContextMenuItem::action("Open Notes", OpenChannelBuffer { channel_id }),
+ ContextMenuItem::action("Open Notes", OpenChannelNotes { channel_id }),
];
if self.channel_store.read(cx).is_user_admin(channel_id) {
@@ -2114,9 +2042,6 @@ impl CollabPanel {
ListEntry::Channel { channel, .. } => {
self.join_channel_chat(channel.id, cx);
}
- ListEntry::ChannelCall { channel, .. } => {
- self.join_channel_call(channel.id, cx);
- }
ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
_ => {}
}
@@ -2325,7 +2250,7 @@ impl CollabPanel {
}
}
- fn open_channel_buffer(&mut self, action: &OpenChannelBuffer, cx: &mut ViewContext<Self>) {
+ fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
if let Some(workspace) = self.workspace.upgrade(cx) {
let pane = workspace.read(cx).active_pane().clone();
let channel_id = action.channel_id;
@@ -2510,7 +2435,9 @@ impl CollabPanel {
.detach_and_log_err(cx);
}
- fn join_channel_chat(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
+ fn join_channel_chat(&mut self, channel_id: u64, cx: &mut ViewContext<Self>) {
+ self.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
+
if let Some(workspace) = self.workspace.upgrade(cx) {
cx.app_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
@@ -2757,18 +2684,6 @@ impl PartialEq for ListEntry {
return channel_1.id == channel_2.id && depth_1 == depth_2;
}
}
- ListEntry::ChannelCall {
- channel: channel_1,
- depth: depth_1,
- } => {
- if let ListEntry::ChannelCall {
- channel: channel_2,
- depth: depth_2,
- } = other
- {
- return channel_1.id == channel_2.id && depth_1 == depth_2;
- }
- }
ListEntry::ChannelNotes { channel_id } => {
if let ListEntry::ChannelNotes {
channel_id: other_id,