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}