diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index f8d28ac96d7c140141ac520b1c38a10c82dd75a9..7bd06159df037a870d5d4a9bb8af36872ec0454f 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -38,6 +38,7 @@ pub struct ChannelStore { channel_invitations: Vec>, channel_participants: HashMap>>, channel_states: HashMap, + favorite_channel_ids: Vec, outgoing_invites: HashSet<(ChannelId, UserId)>, update_channels_tx: mpsc::UnboundedSender, opened_buffers: HashMap>, @@ -160,6 +161,31 @@ impl ChannelStore { cx.try_global::().map(|g| g.0.clone()) } + pub fn favorite_channel_ids(&self) -> &[ChannelId] { + &self.favorite_channel_ids + } + + pub fn is_channel_favorited(&self, channel_id: ChannelId) -> bool { + self.favorite_channel_ids.binary_search(&channel_id).is_ok() + } + + pub fn toggle_favorite_channel(&mut self, channel_id: ChannelId, cx: &mut Context) { + match self.favorite_channel_ids.binary_search(&channel_id) { + Ok(ix) => { + self.favorite_channel_ids.remove(ix); + } + Err(ix) => { + self.favorite_channel_ids.insert(ix, channel_id); + } + } + cx.notify(); + } + + pub fn set_favorite_channel_ids(&mut self, ids: Vec, cx: &mut Context) { + self.favorite_channel_ids = ids; + cx.notify(); + } + pub fn new(client: Arc, user_store: Entity, cx: &mut Context) -> Self { let rpc_subscriptions = [ client.add_message_handler(cx.weak_entity(), Self::handle_update_channels), @@ -217,6 +243,7 @@ impl ChannelStore { .log_err(); }), channel_states: Default::default(), + favorite_channel_ids: Vec::default(), did_subscribe: false, channels_loaded: watch::channel_with(false), } @@ -1066,6 +1093,8 @@ impl ChannelStore { self.channel_index.delete_channels(&delete_channels); self.channel_participants .retain(|channel_id, _| !delete_channels.contains(channel_id)); + self.favorite_channel_ids + .retain(|channel_id| !delete_channels.contains(channel_id)); for channel_id in &delete_channels { let channel_id = *channel_id; diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 328a97ce3296aefbabc284b91da62530b0106359..305e06680e57c7e9c3f1b08d5bdcfc80456313ee 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -9,7 +9,7 @@ use channel::{Channel, ChannelEvent, ChannelStore}; use client::{ChannelId, Client, Contact, User, UserStore}; use collections::{HashMap, HashSet}; use contact_finder::ContactFinder; -use db::kvp::KeyValueStore; +use db::kvp::{GlobalKeyValueStore, KeyValueStore}; use editor::{Editor, EditorElement, EditorStyle}; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; use gpui::{ @@ -259,7 +259,6 @@ pub struct CollabPanel { subscriptions: Vec, collapsed_sections: Vec
, collapsed_channels: Vec, - favorite_channels: Vec, filter_active_channels: bool, workspace: WeakEntity, } @@ -267,8 +266,6 @@ pub struct CollabPanel { #[derive(Serialize, Deserialize)] struct SerializedCollabPanel { collapsed_channels: Option>, - #[serde(default)] - favorite_channels: Option>, } #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] @@ -394,7 +391,6 @@ impl CollabPanel { match_candidates: Vec::default(), collapsed_sections: vec![Section::Offline], collapsed_channels: Vec::default(), - favorite_channels: Vec::default(), filter_active_channels: false, workspace: workspace.weak_handle(), client: workspace.app_state().client.clone(), @@ -472,15 +468,28 @@ impl CollabPanel { .iter() .map(|cid| ChannelId(*cid)) .collect(); - panel.favorite_channels = serialized_panel - .favorite_channels - .unwrap_or_default() - .iter() - .map(|cid| ChannelId(*cid)) - .collect(); cx.notify(); }); } + + let favorites: Vec = GlobalKeyValueStore::global() + .read_kvp("favorite_channels") + .ok() + .flatten() + .and_then(|json| serde_json::from_str::>(&json).ok()) + .unwrap_or_default() + .into_iter() + .map(ChannelId) + .collect(); + + if !favorites.is_empty() { + panel.update(cx, |panel, cx| { + panel.channel_store.update(cx, |store, cx| { + store.set_favorite_channel_ids(favorites, cx); + }); + }); + } + panel }) } @@ -508,21 +517,12 @@ impl CollabPanel { Some(self.collapsed_channels.iter().map(|id| id.0).collect()) }; - let favorite_channels = if self.favorite_channels.is_empty() { - None - } else { - Some(self.favorite_channels.iter().map(|id| id.0).collect()) - }; - let kvp = KeyValueStore::global(cx); self.pending_serialization = cx.background_spawn( async move { kvp.write_kvp( serialization_key, - serde_json::to_string(&SerializedCollabPanel { - collapsed_channels, - favorite_channels, - })?, + serde_json::to_string(&SerializedCollabPanel { collapsed_channels })?, ) .await?; anyhow::Ok(()) @@ -684,21 +684,12 @@ impl CollabPanel { let mut request_entries = Vec::new(); - if self.channel_store.read(cx).channel_count() > 0 { - let previous_len = self.favorite_channels.len(); - self.favorite_channels - .retain(|id| self.channel_store.read(cx).channel_for_id(*id).is_some()); - if self.favorite_channels.len() != previous_len { - self.serialize(cx); - } - } - let channel_store = self.channel_store.read(cx); let user_store = self.user_store.read(cx); - if !self.favorite_channels.is_empty() { - let favorite_channels: Vec<_> = self - .favorite_channels + let favorite_ids = channel_store.favorite_channel_ids(); + if !favorite_ids.is_empty() { + let favorite_channels: Vec<_> = favorite_ids .iter() .filter_map(|id| channel_store.channel_for_id(*id)) .collect(); @@ -1442,7 +1433,7 @@ impl CollabPanel { ) .separator() .entry( - if self.is_channel_favorited(channel_id) { + if self.is_channel_favorited(channel_id, cx) { "Remove from Favorites" } else { "Add to Favorites" @@ -1932,21 +1923,34 @@ impl CollabPanel { } fn toggle_favorite_channel(&mut self, channel_id: ChannelId, cx: &mut Context) { - match self.favorite_channels.binary_search(&channel_id) { - Ok(ix) => { - self.favorite_channels.remove(ix); - } - Err(ix) => { - self.favorite_channels.insert(ix, channel_id); - } - }; - self.serialize(cx); - self.update_entries(true, cx); - cx.notify(); + self.channel_store.update(cx, |store, cx| { + store.toggle_favorite_channel(channel_id, cx); + }); + self.persist_favorites(cx); + } + + fn is_channel_favorited(&self, channel_id: ChannelId, cx: &App) -> bool { + self.channel_store.read(cx).is_channel_favorited(channel_id) } - fn is_channel_favorited(&self, channel_id: ChannelId) -> bool { - self.favorite_channels.binary_search(&channel_id).is_ok() + fn persist_favorites(&mut self, cx: &mut Context) { + let favorite_ids: Vec = self + .channel_store + .read(cx) + .favorite_channel_ids() + .iter() + .map(|id| id.0) + .collect(); + self.pending_serialization = cx.background_spawn( + async move { + let json = serde_json::to_string(&favorite_ids)?; + GlobalKeyValueStore::global() + .write_kvp("favorite_channels".to_string(), json) + .await?; + anyhow::Ok(()) + } + .log_err(), + ); } fn leave_call(window: &mut Window, cx: &mut App) { @@ -3058,7 +3062,7 @@ impl CollabPanel { .unwrap_or(px(240.)); let root_id = channel.root_id(); - let is_favorited = self.is_channel_favorited(channel_id); + let is_favorited = self.is_channel_favorited(channel_id, cx); let (favorite_icon, favorite_color, favorite_tooltip) = if is_favorited { (IconName::StarFilled, Color::Accent, "Remove from Favorites") } else { @@ -3066,7 +3070,7 @@ impl CollabPanel { }; h_flex() - .id(channel_id.0 as usize) + .id(ix) .group("") .h_6() .w_full() @@ -3096,7 +3100,7 @@ impl CollabPanel { }), ) .child( - ListItem::new(channel_id.0 as usize) + ListItem::new(ix) // Add one level of depth for the disclosure arrow. .height(px(26.)) .indent_level(depth + 1)