Add channel notes view

co-authored-by: Max <max@zed.dev>
This commit is contained in:
Mikayla 2023-08-22 14:18:32 -07:00
parent 5a0315c4d5
commit 4eff8ad186
No known key found for this signature in database
9 changed files with 150 additions and 14 deletions

1
Cargo.lock generated
View file

@ -1529,6 +1529,7 @@ dependencies = [
"futures 0.3.28",
"fuzzy",
"gpui",
"language",
"log",
"menu",
"picker",

View file

@ -1,4 +1,4 @@
use crate::ChannelId;
use crate::{Channel, ChannelId, ChannelStore};
use anyhow::Result;
use client::Client;
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
@ -16,6 +16,7 @@ pub struct ChannelBuffer {
channel_id: ChannelId,
collaborators: Vec<proto::Collaborator>,
buffer: ModelHandle<language::Buffer>,
channel_store: ModelHandle<ChannelStore>,
client: Arc<Client>,
_subscription: client::Subscription,
}
@ -33,7 +34,8 @@ impl Entity for ChannelBuffer {
}
impl ChannelBuffer {
pub fn join_channel(
pub(crate) fn new(
channel_store: ModelHandle<ChannelStore>,
channel_id: ChannelId,
client: Arc<Client>,
cx: &mut AppContext,
@ -65,6 +67,7 @@ impl ChannelBuffer {
buffer,
client,
channel_id,
channel_store,
collaborators,
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
}
@ -161,4 +164,11 @@ impl ChannelBuffer {
pub fn collaborators(&self) -> &[proto::Collaborator] {
&self.collaborators
}
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
self.channel_store
.read(cx)
.channel_for_id(self.channel_id)
.cloned()
}
}

View file

@ -13,6 +13,8 @@ use rpc::{proto, TypedEnvelope};
use std::sync::Arc;
use util::ResultExt;
use crate::channel_buffer::ChannelBuffer;
pub type ChannelId = u64;
pub struct ChannelStore {
@ -151,6 +153,14 @@ impl ChannelStore {
self.channels_by_id.get(&channel_id)
}
pub fn open_channel_buffer(
&self,
channel_id: ChannelId,
cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<ChannelBuffer>>> {
ChannelBuffer::new(cx.handle(), channel_id, self.client.clone(), cx)
}
pub fn is_user_admin(&self, channel_id: ChannelId) -> bool {
self.channel_paths.iter().any(|path| {
if let Some(ix) = path.iter().position(|id| *id == channel_id) {

View file

@ -1,6 +1,5 @@
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
use channel::channel_buffer::ChannelBuffer;
use client::UserId;
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
use rpc::{proto, RECEIVE_TIMEOUT};
@ -22,8 +21,9 @@ async fn test_core_channel_buffers(
.await;
// Client A joins the channel buffer
let channel_buffer_a = cx_a
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
let channel_buffer_a = client_a
.channel_store()
.update(cx_a, |channel, cx| channel.open_channel_buffer(zed_id, cx))
.await
.unwrap();
@ -45,8 +45,9 @@ async fn test_core_channel_buffers(
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
// Client B joins the channel buffer
let channel_buffer_b = cx_b
.update(|cx| ChannelBuffer::join_channel(zed_id, client_b.client().to_owned(), cx))
let channel_buffer_b = client_b
.channel_store()
.update(cx_b, |channel, cx| channel.open_channel_buffer(zed_id, cx))
.await
.unwrap();
@ -79,8 +80,9 @@ async fn test_core_channel_buffers(
});
// Client A rejoins the channel buffer
let _channel_buffer_a = cx_a
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
let _channel_buffer_a = client_a
.channel_store()
.update(cx_a, |channels, cx| channels.open_channel_buffer(zed_id, cx))
.await
.unwrap();
deterministic.run_until_parked();
@ -104,7 +106,8 @@ async fn test_core_channel_buffers(
});
// TODO:
// - Test synchronizing offline updates, what happens to A's channel buffer?
// - Test synchronizing offline updates, what happens to A's channel buffer when A disconnects
// - Test interaction with channel deletion while buffer is open
}
#[track_caller]

View file

@ -34,6 +34,7 @@ editor = { path = "../editor" }
feedback = { path = "../feedback" }
fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }
language = { path = "../language" }
menu = { path = "../menu" }
picker = { path = "../picker" }
project = { path = "../project" }

View file

@ -0,0 +1,69 @@
use channel::channel_buffer::ChannelBuffer;
use editor::Editor;
use gpui::{
actions,
elements::{ChildView, Label},
AnyElement, AppContext, Element, Entity, ModelHandle, View, ViewContext, ViewHandle,
};
use language::Language;
use std::sync::Arc;
use workspace::item::{Item, ItemHandle};
actions!(channel_view, [Deploy]);
pub(crate) fn init(cx: &mut AppContext) {
// TODO
}
pub struct ChannelView {
editor: ViewHandle<Editor>,
channel_buffer: ModelHandle<ChannelBuffer>,
}
impl ChannelView {
pub fn new(
channel_buffer: ModelHandle<ChannelBuffer>,
language: Arc<Language>,
cx: &mut ViewContext<Self>,
) -> Self {
let buffer = channel_buffer.read(cx).buffer();
buffer.update(cx, |buffer, cx| buffer.set_language(Some(language), cx));
let editor = cx.add_view(|cx| Editor::for_buffer(buffer, None, cx));
Self {
editor,
channel_buffer,
}
}
}
impl Entity for ChannelView {
type Event = editor::Event;
}
impl View for ChannelView {
fn ui_name() -> &'static str {
"ChannelView"
}
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
ChildView::new(self.editor.as_any(), cx).into_any()
}
}
impl Item for ChannelView {
fn tab_content<V: 'static>(
&self,
_: Option<usize>,
style: &theme::Tab,
cx: &gpui::AppContext,
) -> AnyElement<V> {
let channel_name = self
.channel_buffer
.read(cx)
.channel(cx)
.map_or("[Deleted channel]".to_string(), |channel| {
format!("#{}", channel.name)
});
Label::new(channel_name, style.label.to_owned()).into_any()
}
}

View file

@ -42,7 +42,10 @@ use workspace::{
Workspace,
};
use crate::face_pile::FacePile;
use crate::{
channel_view::{self, ChannelView},
face_pile::FacePile,
};
use channel_modal::ChannelModal;
use self::contact_finder::ContactFinder;
@ -77,6 +80,11 @@ struct RenameChannel {
channel_id: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct OpenChannelBuffer {
channel_id: u64,
}
actions!(
collab_panel,
[
@ -96,7 +104,8 @@ impl_actions!(
InviteMembers,
ManageMembers,
RenameChannel,
ToggleCollapse
ToggleCollapse,
OpenChannelBuffer
]
);
@ -106,6 +115,7 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
settings::register::<panel_settings::CollaborationPanelSettings>(cx);
contact_finder::init(cx);
channel_modal::init(cx);
channel_view::init(cx);
cx.add_action(CollabPanel::cancel);
cx.add_action(CollabPanel::select_next);
@ -121,7 +131,8 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
cx.add_action(CollabPanel::rename_channel);
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::expand_selected_channel);
cx.add_action(CollabPanel::open_channel_buffer);
}
#[derive(Debug)]
@ -1888,6 +1899,7 @@ impl CollabPanel {
vec![
ContextMenuItem::action(expand_action_name, ToggleCollapse { channel_id }),
ContextMenuItem::action("New Subchannel", NewChannel { channel_id }),
ContextMenuItem::action("Open Notes", OpenChannelBuffer { channel_id }),
ContextMenuItem::Separator,
ContextMenuItem::action("Invite to Channel", InviteMembers { channel_id }),
ContextMenuItem::Separator,
@ -2207,6 +2219,34 @@ impl CollabPanel {
}
}
fn open_channel_buffer(&mut self, action: &OpenChannelBuffer, cx: &mut ViewContext<Self>) {
let workspace = self.workspace;
let open = self.channel_store.update(cx, |channel_store, cx| {
channel_store.open_channel_buffer(action.channel_id, cx)
});
cx.spawn(|_, mut cx| async move {
let channel_buffer = open.await?;
let markdown = workspace
.read_with(&cx, |workspace, _| {
workspace
.app_state()
.languages
.language_for_name("Markdown")
})?
.await?;
workspace.update(&mut cx, |workspace, cx| {
let channel_view = cx.add_view(|cx| ChannelView::new(channel_buffer, markdown, cx));
workspace.add_item(Box::new(channel_view), cx);
})?;
anyhow::Ok(())
})
.detach();
}
fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
let Some(channel) = self.selected_channel() else {
return;

View file

@ -1,3 +1,4 @@
pub mod channel_view;
pub mod collab_panel;
mod collab_titlebar_item;
mod contact_notification;

View file

@ -4687,12 +4687,13 @@ impl AnyWeakModelHandle {
}
}
#[derive(Copy)]
pub struct WeakViewHandle<T> {
any_handle: AnyWeakViewHandle,
view_type: PhantomData<T>,
}
impl<T> Copy for WeakViewHandle<T> {}
impl<T> Debug for WeakViewHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(&format!("WeakViewHandle<{}>", type_name::<T>()))