panel.rs

  1mod contacts;
  2mod panel_settings;
  3
  4use std::sync::Arc;
  5
  6use anyhow::Result;
  7use client::Client;
  8use context_menu::ContextMenu;
  9use db::kvp::KEY_VALUE_STORE;
 10use gpui::{
 11    actions,
 12    elements::{ChildView, Flex, Label, ParentElement, Stack},
 13    serde_json, AppContext, AsyncAppContext, Element, Entity, Task, View, ViewContext, ViewHandle,
 14    WeakViewHandle,
 15};
 16use project::Fs;
 17use serde_derive::{Deserialize, Serialize};
 18use settings::SettingsStore;
 19use util::{ResultExt, TryFutureExt};
 20use workspace::{
 21    dock::{DockPosition, Panel},
 22    Workspace,
 23};
 24
 25use self::{
 26    contacts::Contacts,
 27    panel_settings::{ChannelsPanelDockPosition, ChannelsPanelSettings},
 28};
 29
 30actions!(collab_panel, [ToggleFocus]);
 31
 32const CHANNELS_PANEL_KEY: &'static str = "ChannelsPanel";
 33
 34pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
 35    settings::register::<panel_settings::ChannelsPanelSettings>(cx);
 36    contacts::init(cx);
 37}
 38
 39pub struct CollabPanel {
 40    width: Option<f32>,
 41    fs: Arc<dyn Fs>,
 42    has_focus: bool,
 43    pending_serialization: Task<Option<()>>,
 44    context_menu: ViewHandle<ContextMenu>,
 45    contacts: ViewHandle<contacts::Contacts>,
 46}
 47
 48#[derive(Serialize, Deserialize)]
 49struct SerializedChannelsPanel {
 50    width: Option<f32>,
 51}
 52
 53#[derive(Debug)]
 54pub enum Event {
 55    DockPositionChanged,
 56    Focus,
 57}
 58
 59impl Entity for CollabPanel {
 60    type Event = Event;
 61}
 62
 63impl CollabPanel {
 64    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
 65        cx.add_view(|cx| {
 66            let view_id = cx.view_id();
 67
 68            let this = Self {
 69                width: None,
 70                has_focus: false,
 71                fs: workspace.app_state().fs.clone(),
 72                pending_serialization: Task::ready(None),
 73                context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
 74                contacts: cx.add_view(|cx| {
 75                    Contacts::new(
 76                        workspace.project().clone(),
 77                        workspace.user_store().clone(),
 78                        workspace.weak_handle(),
 79                        cx,
 80                    )
 81                }),
 82            };
 83
 84            // Update the dock position when the setting changes.
 85            let mut old_dock_position = this.position(cx);
 86            cx.observe_global::<SettingsStore, _>(move |this: &mut CollabPanel, cx| {
 87                let new_dock_position = this.position(cx);
 88                if new_dock_position != old_dock_position {
 89                    old_dock_position = new_dock_position;
 90                    cx.emit(Event::DockPositionChanged);
 91                }
 92            })
 93            .detach();
 94
 95            this
 96        })
 97    }
 98
 99    pub fn load(
100        workspace: WeakViewHandle<Workspace>,
101        cx: AsyncAppContext,
102    ) -> Task<Result<ViewHandle<Self>>> {
103        cx.spawn(|mut cx| async move {
104            let serialized_panel = if let Some(panel) = cx
105                .background()
106                .spawn(async move { KEY_VALUE_STORE.read_kvp(CHANNELS_PANEL_KEY) })
107                .await
108                .log_err()
109                .flatten()
110            {
111                Some(serde_json::from_str::<SerializedChannelsPanel>(&panel)?)
112            } else {
113                None
114            };
115
116            workspace.update(&mut cx, |workspace, cx| {
117                let panel = CollabPanel::new(workspace, cx);
118                if let Some(serialized_panel) = serialized_panel {
119                    panel.update(cx, |panel, cx| {
120                        panel.width = serialized_panel.width;
121                        cx.notify();
122                    });
123                }
124                panel
125            })
126        })
127    }
128
129    fn serialize(&mut self, cx: &mut ViewContext<Self>) {
130        let width = self.width;
131        self.pending_serialization = cx.background().spawn(
132            async move {
133                KEY_VALUE_STORE
134                    .write_kvp(
135                        CHANNELS_PANEL_KEY.into(),
136                        serde_json::to_string(&SerializedChannelsPanel { width })?,
137                    )
138                    .await?;
139                anyhow::Ok(())
140            }
141            .log_err(),
142        );
143    }
144}
145
146impl View for CollabPanel {
147    fn ui_name() -> &'static str {
148        "ChannelsPanel"
149    }
150
151    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
152        if !self.has_focus {
153            self.has_focus = true;
154            cx.emit(Event::Focus);
155        }
156    }
157
158    fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
159        self.has_focus = false;
160    }
161
162    fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
163        let theme = theme::current(cx).clone();
164
165        enum ChannelsPanelScrollTag {}
166        Stack::new()
167            .with_child(
168                // Full panel column
169                Flex::column()
170                    .with_child(
171                        Flex::column()
172                            .with_child(
173                                Flex::row().with_child(
174                                    Label::new(
175                                        "Contacts",
176                                        theme.editor.invalid_information_diagnostic.message.clone(),
177                                    )
178                                    .into_any(),
179                                ),
180                            )
181                            .with_child(ChildView::new(&self.contacts, cx)),
182                    )
183                    .scrollable::<ChannelsPanelScrollTag>(0, None, cx),
184            )
185            .with_child(ChildView::new(&self.context_menu, cx))
186            .into_any_named("channels panel")
187            .into_any()
188    }
189}
190
191impl Panel for CollabPanel {
192    fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
193        match settings::get::<ChannelsPanelSettings>(cx).dock {
194            ChannelsPanelDockPosition::Left => DockPosition::Left,
195            ChannelsPanelDockPosition::Right => DockPosition::Right,
196        }
197    }
198
199    fn position_is_valid(&self, position: DockPosition) -> bool {
200        matches!(position, DockPosition::Left | DockPosition::Right)
201    }
202
203    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
204        settings::update_settings_file::<ChannelsPanelSettings>(
205            self.fs.clone(),
206            cx,
207            move |settings| {
208                let dock = match position {
209                    DockPosition::Left | DockPosition::Bottom => ChannelsPanelDockPosition::Left,
210                    DockPosition::Right => ChannelsPanelDockPosition::Right,
211                };
212                settings.dock = Some(dock);
213            },
214        );
215    }
216
217    fn size(&self, cx: &gpui::WindowContext) -> f32 {
218        self.width
219            .unwrap_or_else(|| settings::get::<ChannelsPanelSettings>(cx).default_width)
220    }
221
222    fn set_size(&mut self, size: f32, cx: &mut ViewContext<Self>) {
223        self.width = Some(size);
224        self.serialize(cx);
225        cx.notify();
226    }
227
228    fn icon_path(&self) -> &'static str {
229        "icons/radix/person.svg"
230    }
231
232    fn icon_tooltip(&self) -> (String, Option<Box<dyn gpui::Action>>) {
233        ("Channels Panel".to_string(), Some(Box::new(ToggleFocus)))
234    }
235
236    fn should_change_position_on_event(event: &Self::Event) -> bool {
237        matches!(event, Event::DockPositionChanged)
238    }
239
240    fn has_focus(&self, _cx: &gpui::WindowContext) -> bool {
241        self.has_focus
242    }
243
244    fn is_focus_event(event: &Self::Event) -> bool {
245        matches!(event, Event::Focus)
246    }
247}