mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 15:44:20 +03:00
WIP: Add channel creation to panel UI
This commit is contained in:
parent
7954b02819
commit
7434d66fdd
@ -33,6 +33,7 @@ impl ChannelStore {
|
||||
) -> Self {
|
||||
let rpc_subscription =
|
||||
client.add_message_handler(cx.handle(), Self::handle_update_channels);
|
||||
|
||||
Self {
|
||||
channels: vec![],
|
||||
channel_invitations: vec![],
|
||||
|
@ -3214,6 +3214,44 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_channel_invites(&self, user_id: UserId) -> Result<Vec<Channel>> {
|
||||
self.transaction(|tx| async move {
|
||||
let tx = tx;
|
||||
|
||||
let channel_invites = channel_member::Entity::find()
|
||||
.filter(
|
||||
channel_member::Column::UserId
|
||||
.eq(user_id)
|
||||
.and(channel_member::Column::Accepted.eq(false)),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let channels = channel::Entity::find()
|
||||
.filter(
|
||||
channel::Column::Id.is_in(
|
||||
channel_invites
|
||||
.into_iter()
|
||||
.map(|channel_member| channel_member.channel_id),
|
||||
),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let channels = channels
|
||||
.into_iter()
|
||||
.map(|channel| Channel {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
parent_id: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(channels)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_channels(&self, user_id: UserId) -> Result<Vec<Channel>> {
|
||||
self.transaction(|tx| async move {
|
||||
let tx = tx;
|
||||
|
@ -1023,6 +1023,96 @@ test_both_dbs!(
|
||||
}
|
||||
);
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_invites_postgres,
|
||||
test_channel_invites_sqlite,
|
||||
db,
|
||||
{
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
|
||||
let user_1 = db
|
||||
.create_user(
|
||||
"user1@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user1".into(),
|
||||
github_user_id: 5,
|
||||
invite_count: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
let user_2 = db
|
||||
.create_user(
|
||||
"user2@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user2".into(),
|
||||
github_user_id: 6,
|
||||
invite_count: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let user_3 = db
|
||||
.create_user(
|
||||
"user3@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user3".into(),
|
||||
github_user_id: 7,
|
||||
invite_count: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let channel_1_1 = db
|
||||
.create_root_channel("channel_1", "1", user_1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let channel_1_2 = db
|
||||
.create_root_channel("channel_2", "2", user_1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.invite_channel_member(channel_1_1, user_2, user_1, false)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(channel_1_2, user_2, user_1, false)
|
||||
.await
|
||||
.unwrap();
|
||||
db.invite_channel_member(channel_1_1, user_3, user_1, false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user_2_invites = db
|
||||
.get_channel_invites(user_2) // -> [channel_1_1, channel_1_2]
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|channel| channel.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
|
||||
|
||||
let user_3_invites = db
|
||||
.get_channel_invites(user_3) // -> [channel_1_1]
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|channel| channel.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(user_3_invites, &[channel_1_1])
|
||||
}
|
||||
);
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_signup_overwrite() {
|
||||
let test_db = TestDb::postgres(build_background_executor());
|
||||
|
@ -516,15 +516,19 @@ impl Server {
|
||||
this.app_state.db.set_user_connected_once(user_id, true).await?;
|
||||
}
|
||||
|
||||
let (contacts, invite_code) = future::try_join(
|
||||
let (contacts, invite_code, channels, channel_invites) = future::try_join4(
|
||||
this.app_state.db.get_contacts(user_id),
|
||||
this.app_state.db.get_invite_code_for_user(user_id)
|
||||
this.app_state.db.get_invite_code_for_user(user_id),
|
||||
this.app_state.db.get_channels(user_id),
|
||||
this.app_state.db.get_channel_invites(user_id)
|
||||
).await?;
|
||||
|
||||
{
|
||||
let mut pool = this.connection_pool.lock();
|
||||
pool.add_connection(connection_id, user_id, user.admin);
|
||||
this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
|
||||
this.peer.send(connection_id, build_initial_channels_update(channels, channel_invites))?;
|
||||
|
||||
|
||||
if let Some((code, count)) = invite_code {
|
||||
this.peer.send(connection_id, proto::UpdateInviteInfo {
|
||||
@ -2097,6 +2101,7 @@ async fn create_channel(
|
||||
response: Response<proto::CreateChannel>,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
dbg!(&request);
|
||||
let db = session.db().await;
|
||||
let live_kit_room = format!("channel-{}", nanoid::nanoid!(30));
|
||||
|
||||
@ -2307,6 +2312,31 @@ fn to_tungstenite_message(message: AxumMessage) -> TungsteniteMessage {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_initial_channels_update(
|
||||
channels: Vec<db::Channel>,
|
||||
channel_invites: Vec<db::Channel>,
|
||||
) -> proto::UpdateChannels {
|
||||
let mut update = proto::UpdateChannels::default();
|
||||
|
||||
for channel in channels {
|
||||
update.channels.push(proto::Channel {
|
||||
id: channel.id.to_proto(),
|
||||
name: channel.name,
|
||||
parent_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
for channel in channel_invites {
|
||||
update.channel_invitations.push(proto::Channel {
|
||||
id: channel.id.to_proto(),
|
||||
name: channel.name,
|
||||
parent_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
update
|
||||
}
|
||||
|
||||
fn build_initial_contacts_update(
|
||||
contacts: Vec<db::Contact>,
|
||||
pool: &ConnectionPool,
|
||||
|
@ -32,11 +32,10 @@ use theme::IconButton;
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel},
|
||||
item::ItemHandle,
|
||||
Workspace,
|
||||
};
|
||||
|
||||
use self::channel_modal::ChannelModal;
|
||||
|
||||
actions!(collab_panel, [ToggleFocus]);
|
||||
|
||||
const CHANNELS_PANEL_KEY: &'static str = "ChannelsPanel";
|
||||
@ -52,6 +51,11 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
|
||||
cx.add_action(CollabPanel::confirm);
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ChannelEditingState {
|
||||
root_channel: bool,
|
||||
}
|
||||
|
||||
pub struct CollabPanel {
|
||||
width: Option<f32>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@ -59,6 +63,8 @@ pub struct CollabPanel {
|
||||
pending_serialization: Task<Option<()>>,
|
||||
context_menu: ViewHandle<ContextMenu>,
|
||||
filter_editor: ViewHandle<Editor>,
|
||||
channel_name_editor: ViewHandle<Editor>,
|
||||
channel_editing_state: Option<ChannelEditingState>,
|
||||
entries: Vec<ContactEntry>,
|
||||
selection: Option<usize>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
@ -93,7 +99,7 @@ enum Section {
|
||||
Offline,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
enum ContactEntry {
|
||||
Header(Section, usize),
|
||||
CallParticipant {
|
||||
@ -157,6 +163,23 @@ impl CollabPanel {
|
||||
})
|
||||
.detach();
|
||||
|
||||
let channel_name_editor = cx.add_view(|cx| {
|
||||
Editor::single_line(
|
||||
Some(Arc::new(|theme| {
|
||||
theme.collab_panel.user_query_editor.clone()
|
||||
})),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.subscribe(&channel_name_editor, |this, _, event, cx| {
|
||||
if let editor::Event::Blurred = event {
|
||||
this.take_editing_state(cx);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let list_state =
|
||||
ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
|
||||
let theme = theme::current(cx).clone();
|
||||
@ -166,7 +189,7 @@ impl CollabPanel {
|
||||
match &this.entries[ix] {
|
||||
ContactEntry::Header(section, depth) => {
|
||||
let is_collapsed = this.collapsed_sections.contains(section);
|
||||
Self::render_header(
|
||||
this.render_header(
|
||||
*section,
|
||||
&theme,
|
||||
*depth,
|
||||
@ -250,8 +273,10 @@ impl CollabPanel {
|
||||
fs: workspace.app_state().fs.clone(),
|
||||
pending_serialization: Task::ready(None),
|
||||
context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
|
||||
channel_name_editor,
|
||||
filter_editor,
|
||||
entries: Vec::default(),
|
||||
channel_editing_state: None,
|
||||
selection: None,
|
||||
user_store: workspace.user_store().clone(),
|
||||
channel_store: workspace.app_state().channel_store.clone(),
|
||||
@ -333,6 +358,13 @@ impl CollabPanel {
|
||||
);
|
||||
}
|
||||
|
||||
fn is_editing_root_channel(&self) -> bool {
|
||||
self.channel_editing_state
|
||||
.as_ref()
|
||||
.map(|state| state.root_channel)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn update_entries(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let user_store = self.user_store.read(cx);
|
||||
@ -944,7 +976,23 @@ impl CollabPanel {
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn take_editing_state(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<(ChannelEditingState, String)> {
|
||||
let result = self
|
||||
.channel_editing_state
|
||||
.take()
|
||||
.map(|state| (state, self.channel_name_editor.read(cx).text(cx)));
|
||||
|
||||
self.channel_name_editor
|
||||
.update(cx, |editor, cx| editor.set_text("", cx));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn render_header(
|
||||
&self,
|
||||
section: Section,
|
||||
theme: &theme::Theme,
|
||||
depth: usize,
|
||||
@ -1014,7 +1062,13 @@ impl CollabPanel {
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, this, cx| {
|
||||
this.toggle_channel_finder(cx);
|
||||
if this.channel_editing_state.is_none() {
|
||||
this.channel_editing_state =
|
||||
Some(ChannelEditingState { root_channel: true });
|
||||
}
|
||||
|
||||
cx.focus(this.channel_name_editor.as_any());
|
||||
cx.notify();
|
||||
})
|
||||
.with_tooltip::<AddChannel>(
|
||||
0,
|
||||
@ -1027,6 +1081,13 @@ impl CollabPanel {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let addition = match section {
|
||||
Section::Channels if self.is_editing_root_channel() => {
|
||||
Some(ChildView::new(self.channel_name_editor.as_any(), cx))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let can_collapse = depth > 0;
|
||||
let icon_size = (&theme.collab_panel).section_icon_size;
|
||||
MouseEventHandler::<Header, Self>::new(section as usize, cx, |state, _| {
|
||||
@ -1040,40 +1101,44 @@ impl CollabPanel {
|
||||
&theme.collab_panel.header_row
|
||||
};
|
||||
|
||||
Flex::row()
|
||||
.with_children(if can_collapse {
|
||||
Some(
|
||||
Svg::new(if is_collapsed {
|
||||
"icons/chevron_right_8.svg"
|
||||
} else {
|
||||
"icons/chevron_down_8.svg"
|
||||
})
|
||||
.with_color(header_style.text.color)
|
||||
.constrained()
|
||||
.with_max_width(icon_size)
|
||||
.with_max_height(icon_size)
|
||||
.aligned()
|
||||
.constrained()
|
||||
.with_width(icon_size)
|
||||
.contained()
|
||||
.with_margin_right(
|
||||
theme.collab_panel.contact_username.container.margin.left,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Label::new(text, header_style.text.clone())
|
||||
.aligned()
|
||||
.left()
|
||||
.flex(1., true),
|
||||
Flex::row()
|
||||
.with_children(if can_collapse {
|
||||
Some(
|
||||
Svg::new(if is_collapsed {
|
||||
"icons/chevron_right_8.svg"
|
||||
} else {
|
||||
"icons/chevron_down_8.svg"
|
||||
})
|
||||
.with_color(header_style.text.color)
|
||||
.constrained()
|
||||
.with_max_width(icon_size)
|
||||
.with_max_height(icon_size)
|
||||
.aligned()
|
||||
.constrained()
|
||||
.with_width(icon_size)
|
||||
.contained()
|
||||
.with_margin_right(
|
||||
theme.collab_panel.contact_username.container.margin.left,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.with_child(
|
||||
Label::new(text, header_style.text.clone())
|
||||
.aligned()
|
||||
.left()
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_children(button.map(|button| button.aligned().right()))
|
||||
.constrained()
|
||||
.with_height(theme.collab_panel.row_height)
|
||||
.contained()
|
||||
.with_style(header_style.container),
|
||||
)
|
||||
.with_children(button.map(|button| button.aligned().right()))
|
||||
.constrained()
|
||||
.with_height(theme.collab_panel.row_height)
|
||||
.contained()
|
||||
.with_style(header_style.container)
|
||||
.with_children(addition)
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||
@ -1189,7 +1254,7 @@ impl CollabPanel {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> AnyElement<Self> {
|
||||
let channel_id = channel.id;
|
||||
MouseEventHandler::<Channel, Self>::new(channel.id as usize, cx, |state, cx| {
|
||||
MouseEventHandler::<Channel, Self>::new(channel.id as usize, cx, |state, _cx| {
|
||||
Flex::row()
|
||||
.with_child({
|
||||
Svg::new("icons/hash")
|
||||
@ -1218,7 +1283,7 @@ impl CollabPanel {
|
||||
|
||||
fn render_channel_invite(
|
||||
channel: Arc<Channel>,
|
||||
user_store: ModelHandle<ChannelStore>,
|
||||
channel_store: ModelHandle<ChannelStore>,
|
||||
theme: &theme::CollabPanel,
|
||||
is_selected: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
@ -1227,7 +1292,7 @@ impl CollabPanel {
|
||||
enum Accept {}
|
||||
|
||||
let channel_id = channel.id;
|
||||
let is_invite_pending = user_store.read(cx).is_channel_invite_pending(&channel);
|
||||
let is_invite_pending = channel_store.read(cx).is_channel_invite_pending(&channel);
|
||||
let button_spacing = theme.contact_button_spacing;
|
||||
|
||||
Flex::row()
|
||||
@ -1401,7 +1466,7 @@ impl CollabPanel {
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
let did_clear = self.filter_editor.update(cx, |editor, cx| {
|
||||
let mut did_clear = self.filter_editor.update(cx, |editor, cx| {
|
||||
if editor.buffer().read(cx).len(cx) > 0 {
|
||||
editor.set_text("", cx);
|
||||
true
|
||||
@ -1410,6 +1475,8 @@ impl CollabPanel {
|
||||
}
|
||||
});
|
||||
|
||||
did_clear |= self.take_editing_state(cx).is_some();
|
||||
|
||||
if !did_clear {
|
||||
cx.emit(Event::Dismissed);
|
||||
}
|
||||
@ -1496,6 +1563,17 @@ impl CollabPanel {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else if let Some((_editing_state, channel_name)) = self.take_editing_state(cx) {
|
||||
dbg!(&channel_name);
|
||||
let create_channel = self.channel_store.update(cx, |channel_store, cx| {
|
||||
channel_store.create_channel(&channel_name, None)
|
||||
});
|
||||
|
||||
cx.foreground()
|
||||
.spawn(async move {
|
||||
dbg!(create_channel.await).ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1522,14 +1600,6 @@ impl CollabPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_channel_finder(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(workspace) = self.workspace.upgrade(cx) {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| ChannelModal::new(cx)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_contact(&mut self, user_id: u64, github_login: &str, cx: &mut ViewContext<Self>) {
|
||||
let user_store = self.user_store.clone();
|
||||
let prompt_message = format!(
|
||||
|
@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
ZED_ADMIN_API_TOKEN=secret ZED_SERVER_URL=http://localhost:3000 cargo run $@
|
||||
ZED_ADMIN_API_TOKEN=secret ZED_IMPERSONATE=as-cii ZED_SERVER_URL=http://localhost:8080 cargo run $@
|
||||
|
Loading…
Reference in New Issue
Block a user