Wire through public access toggle

This commit is contained in:
Conrad Irwin 2023-10-13 16:59:30 -06:00
parent f8fd77b83e
commit f6f9b5c8cb
13 changed files with 209 additions and 38 deletions

View file

@ -9,7 +9,7 @@ use db::RELEASE_CHANNEL;
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle}; use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
use rpc::{ use rpc::{
proto::{self, ChannelEdge, ChannelPermission, ChannelRole}, proto::{self, ChannelEdge, ChannelPermission, ChannelRole, ChannelVisibility},
TypedEnvelope, TypedEnvelope,
}; };
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -49,6 +49,7 @@ pub type ChannelData = (Channel, ChannelPath);
pub struct Channel { pub struct Channel {
pub id: ChannelId, pub id: ChannelId,
pub name: String, pub name: String,
pub visibility: proto::ChannelVisibility,
pub unseen_note_version: Option<(u64, clock::Global)>, pub unseen_note_version: Option<(u64, clock::Global)>,
pub unseen_message_id: Option<u64>, pub unseen_message_id: Option<u64>,
} }
@ -508,6 +509,25 @@ impl ChannelStore {
}) })
} }
pub fn set_channel_visibility(
&mut self,
channel_id: ChannelId,
visibility: ChannelVisibility,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let client = self.client.clone();
cx.spawn(|_, _| async move {
let _ = client
.request(proto::SetChannelVisibility {
channel_id,
visibility: visibility.into(),
})
.await?;
Ok(())
})
}
pub fn invite_member( pub fn invite_member(
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
@ -869,6 +889,7 @@ impl ChannelStore {
ix, ix,
Arc::new(Channel { Arc::new(Channel {
id: channel.id, id: channel.id,
visibility: channel.visibility(),
name: channel.name, name: channel.name,
unseen_note_version: None, unseen_note_version: None,
unseen_message_id: None, unseen_message_id: None,

View file

@ -123,12 +123,14 @@ impl<'a> ChannelPathsInsertGuard<'a> {
pub fn insert(&mut self, channel_proto: proto::Channel) { pub fn insert(&mut self, channel_proto: proto::Channel) {
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) { if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
Arc::make_mut(existing_channel).visibility = channel_proto.visibility();
Arc::make_mut(existing_channel).name = channel_proto.name; Arc::make_mut(existing_channel).name = channel_proto.name;
} else { } else {
self.channels_by_id.insert( self.channels_by_id.insert(
channel_proto.id, channel_proto.id,
Arc::new(Channel { Arc::new(Channel {
id: channel_proto.id, id: channel_proto.id,
visibility: channel_proto.visibility(),
name: channel_proto.name, name: channel_proto.name,
unseen_note_version: None, unseen_note_version: None,
unseen_message_id: None, unseen_message_id: None,

View file

@ -432,6 +432,7 @@ pub struct NewUserResult {
pub struct Channel { pub struct Channel {
pub id: ChannelId, pub id: ChannelId,
pub name: String, pub name: String,
pub visibility: ChannelVisibility,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]

View file

@ -137,7 +137,7 @@ impl Into<i32> for ChannelRole {
} }
} }
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default)] #[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]
#[sea_orm(rs_type = "String", db_type = "String(None)")] #[sea_orm(rs_type = "String", db_type = "String(None)")]
pub enum ChannelVisibility { pub enum ChannelVisibility {
#[sea_orm(string_value = "public")] #[sea_orm(string_value = "public")]
@ -151,7 +151,7 @@ impl From<proto::ChannelVisibility> for ChannelVisibility {
fn from(value: proto::ChannelVisibility) -> Self { fn from(value: proto::ChannelVisibility) -> Self {
match value { match value {
proto::ChannelVisibility::Public => ChannelVisibility::Public, proto::ChannelVisibility::Public => ChannelVisibility::Public,
proto::ChannelVisibility::ChannelMembers => ChannelVisibility::Members, proto::ChannelVisibility::Members => ChannelVisibility::Members,
} }
} }
} }
@ -160,7 +160,7 @@ impl Into<proto::ChannelVisibility> for ChannelVisibility {
fn into(self) -> proto::ChannelVisibility { fn into(self) -> proto::ChannelVisibility {
match self { match self {
ChannelVisibility::Public => proto::ChannelVisibility::Public, ChannelVisibility::Public => proto::ChannelVisibility::Public,
ChannelVisibility::Members => proto::ChannelVisibility::ChannelMembers, ChannelVisibility::Members => proto::ChannelVisibility::Members,
} }
} }
} }

View file

@ -93,12 +93,12 @@ impl Database {
channel_id: ChannelId, channel_id: ChannelId,
visibility: ChannelVisibility, visibility: ChannelVisibility,
user_id: UserId, user_id: UserId,
) -> Result<()> { ) -> Result<channel::Model> {
self.transaction(move |tx| async move { self.transaction(move |tx| async move {
self.check_user_is_channel_admin(channel_id, user_id, &*tx) self.check_user_is_channel_admin(channel_id, user_id, &*tx)
.await?; .await?;
channel::ActiveModel { let channel = channel::ActiveModel {
id: ActiveValue::Unchanged(channel_id), id: ActiveValue::Unchanged(channel_id),
visibility: ActiveValue::Set(visibility), visibility: ActiveValue::Set(visibility),
..Default::default() ..Default::default()
@ -106,7 +106,7 @@ impl Database {
.update(&*tx) .update(&*tx)
.await?; .await?;
Ok(()) Ok(channel)
}) })
.await .await
} }
@ -219,14 +219,14 @@ impl Database {
channel_id: ChannelId, channel_id: ChannelId,
user_id: UserId, user_id: UserId,
new_name: &str, new_name: &str,
) -> Result<String> { ) -> Result<Channel> {
self.transaction(move |tx| async move { self.transaction(move |tx| async move {
let new_name = Self::sanitize_channel_name(new_name)?.to_string(); let new_name = Self::sanitize_channel_name(new_name)?.to_string();
self.check_user_is_channel_admin(channel_id, user_id, &*tx) self.check_user_is_channel_admin(channel_id, user_id, &*tx)
.await?; .await?;
channel::ActiveModel { let channel = channel::ActiveModel {
id: ActiveValue::Unchanged(channel_id), id: ActiveValue::Unchanged(channel_id),
name: ActiveValue::Set(new_name.clone()), name: ActiveValue::Set(new_name.clone()),
..Default::default() ..Default::default()
@ -234,7 +234,11 @@ impl Database {
.update(&*tx) .update(&*tx)
.await?; .await?;
Ok(new_name) Ok(Channel {
id: channel.id,
name: channel.name,
visibility: channel.visibility,
})
}) })
.await .await
} }
@ -336,6 +340,7 @@ impl Database {
.map(|channel| Channel { .map(|channel| Channel {
id: channel.id, id: channel.id,
name: channel.name, name: channel.name,
visibility: channel.visibility,
}) })
.collect(); .collect();
@ -443,6 +448,7 @@ impl Database {
channels.push(Channel { channels.push(Channel {
id: channel.id, id: channel.id,
name: channel.name, name: channel.name,
visibility: channel.visibility,
}); });
if role == ChannelRole::Admin { if role == ChannelRole::Admin {
@ -963,6 +969,7 @@ impl Database {
Ok(Some(( Ok(Some((
Channel { Channel {
id: channel.id, id: channel.id,
visibility: channel.visibility,
name: channel.name, name: channel.name,
}, },
is_accepted, is_accepted,

View file

@ -159,6 +159,7 @@ fn graph(channels: &[(ChannelId, &'static str)], edges: &[(ChannelId, ChannelId)
graph.channels.push(Channel { graph.channels.push(Channel {
id: *id, id: *id,
name: name.to_string(), name: name.to_string(),
visibility: ChannelVisibility::Members,
}) })
} }

View file

@ -3,8 +3,8 @@ mod connection_pool;
use crate::{ use crate::{
auth, auth,
db::{ db::{
self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId, self, BufferId, ChannelId, ChannelVisibility, ChannelsForUser, Database, MessageId,
ServerId, User, UserId, ProjectId, RoomId, ServerId, User, UserId,
}, },
executor::Executor, executor::Executor,
AppState, Result, AppState, Result,
@ -38,8 +38,8 @@ use lazy_static::lazy_static;
use prometheus::{register_int_gauge, IntGauge}; use prometheus::{register_int_gauge, IntGauge};
use rpc::{ use rpc::{
proto::{ proto::{
self, Ack, AnyTypedEnvelope, ChannelEdge, ChannelVisibility, EntityMessage, self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage,
EnvelopedMessage, LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators, LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators,
}, },
Connection, ConnectionId, Peer, Receipt, TypedEnvelope, Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
}; };
@ -255,6 +255,7 @@ impl Server {
.add_request_handler(invite_channel_member) .add_request_handler(invite_channel_member)
.add_request_handler(remove_channel_member) .add_request_handler(remove_channel_member)
.add_request_handler(set_channel_member_role) .add_request_handler(set_channel_member_role)
.add_request_handler(set_channel_visibility)
.add_request_handler(rename_channel) .add_request_handler(rename_channel)
.add_request_handler(join_channel_buffer) .add_request_handler(join_channel_buffer)
.add_request_handler(leave_channel_buffer) .add_request_handler(leave_channel_buffer)
@ -2210,8 +2211,7 @@ async fn create_channel(
let channel = proto::Channel { let channel = proto::Channel {
id: id.to_proto(), id: id.to_proto(),
name: request.name, name: request.name,
// TODO: Visibility visibility: proto::ChannelVisibility::Members as i32,
visibility: proto::ChannelVisibility::ChannelMembers as i32,
}; };
response.send(proto::CreateChannelResponse { response.send(proto::CreateChannelResponse {
@ -2300,9 +2300,8 @@ async fn invite_channel_member(
let mut update = proto::UpdateChannels::default(); let mut update = proto::UpdateChannels::default();
update.channel_invitations.push(proto::Channel { update.channel_invitations.push(proto::Channel {
id: channel.id.to_proto(), id: channel.id.to_proto(),
visibility: channel.visibility.into(),
name: channel.name, name: channel.name,
// TODO: Visibility
visibility: proto::ChannelVisibility::ChannelMembers as i32,
}); });
for connection_id in session for connection_id in session
.connection_pool() .connection_pool()
@ -2343,6 +2342,39 @@ async fn remove_channel_member(
Ok(()) Ok(())
} }
async fn set_channel_visibility(
request: proto::SetChannelVisibility,
response: Response<proto::SetChannelVisibility>,
session: Session,
) -> Result<()> {
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
let visibility = request.visibility().into();
let channel = db
.set_channel_visibility(channel_id, visibility, session.user_id)
.await?;
let mut update = proto::UpdateChannels::default();
update.channels.push(proto::Channel {
id: channel.id.to_proto(),
name: channel.name,
visibility: channel.visibility.into(),
});
let member_ids = db.get_channel_members(channel_id).await?;
let connection_pool = session.connection_pool().await;
for member_id in member_ids {
for connection_id in connection_pool.user_connection_ids(member_id) {
session.peer.send(connection_id, update.clone())?;
}
}
response.send(proto::Ack {})?;
Ok(())
}
async fn set_channel_member_role( async fn set_channel_member_role(
request: proto::SetChannelMemberRole, request: proto::SetChannelMemberRole,
response: Response<proto::SetChannelMemberRole>, response: Response<proto::SetChannelMemberRole>,
@ -2391,15 +2423,14 @@ async fn rename_channel(
) -> Result<()> { ) -> Result<()> {
let db = session.db().await; let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id); let channel_id = ChannelId::from_proto(request.channel_id);
let new_name = db let channel = db
.rename_channel(channel_id, session.user_id, &request.name) .rename_channel(channel_id, session.user_id, &request.name)
.await?; .await?;
let channel = proto::Channel { let channel = proto::Channel {
id: request.channel_id, id: channel.id.to_proto(),
name: new_name, name: channel.name,
// TODO: Visibility visibility: channel.visibility.into(),
visibility: proto::ChannelVisibility::ChannelMembers as i32,
}; };
response.send(proto::RenameChannelResponse { response.send(proto::RenameChannelResponse {
channel: Some(channel.clone()), channel: Some(channel.clone()),
@ -2437,9 +2468,8 @@ async fn link_channel(
.into_iter() .into_iter()
.map(|channel| proto::Channel { .map(|channel| proto::Channel {
id: channel.id.to_proto(), id: channel.id.to_proto(),
visibility: channel.visibility.into(),
name: channel.name, name: channel.name,
// TODO: Visibility
visibility: proto::ChannelVisibility::ChannelMembers as i32,
}) })
.collect(), .collect(),
insert_edge: channels_to_send.edges, insert_edge: channels_to_send.edges,
@ -2530,9 +2560,8 @@ async fn move_channel(
.into_iter() .into_iter()
.map(|channel| proto::Channel { .map(|channel| proto::Channel {
id: channel.id.to_proto(), id: channel.id.to_proto(),
visibility: channel.visibility.into(),
name: channel.name, name: channel.name,
// TODO: Visibility
visibility: proto::ChannelVisibility::ChannelMembers as i32,
}) })
.collect(), .collect(),
insert_edge: channels_to_send.edges, insert_edge: channels_to_send.edges,
@ -2588,9 +2617,8 @@ async fn respond_to_channel_invite(
.into_iter() .into_iter()
.map(|channel| proto::Channel { .map(|channel| proto::Channel {
id: channel.id.to_proto(), id: channel.id.to_proto(),
visibility: channel.visibility.into(),
name: channel.name, name: channel.name,
// TODO: Visibility
visibility: ChannelVisibility::ChannelMembers.into(),
}), }),
); );
update.unseen_channel_messages = result.channel_messages; update.unseen_channel_messages = result.channel_messages;
@ -3094,8 +3122,7 @@ fn build_initial_channels_update(
update.channels.push(proto::Channel { update.channels.push(proto::Channel {
id: channel.id.to_proto(), id: channel.id.to_proto(),
name: channel.name, name: channel.name,
// TODO: Visibility visibility: channel.visibility.into(),
visibility: ChannelVisibility::Public.into(),
}); });
} }

View file

@ -11,7 +11,10 @@ use collections::HashMap;
use editor::{Anchor, Editor, ToOffset}; use editor::{Anchor, Editor, ToOffset};
use futures::future; use futures::future;
use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext}; use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
use rpc::{proto::PeerId, RECEIVE_TIMEOUT}; use rpc::{
proto::{self, PeerId},
RECEIVE_TIMEOUT,
};
use serde_json::json; use serde_json::json;
use std::{ops::Range, sync::Arc}; use std::{ops::Range, sync::Arc};
@ -445,6 +448,7 @@ fn channel(id: u64, name: &'static str) -> Channel {
Channel { Channel {
id, id,
name: name.to_string(), name: name.to_string(),
visibility: proto::ChannelVisibility::Members,
unseen_note_version: None, unseen_note_version: None,
unseen_message_id: None, unseen_message_id: None,
} }

View file

@ -1,6 +1,6 @@
use channel::{ChannelId, ChannelMembership, ChannelStore}; use channel::{Channel, ChannelId, ChannelMembership, ChannelStore};
use client::{ use client::{
proto::{self, ChannelRole}, proto::{self, ChannelRole, ChannelVisibility},
User, UserId, UserStore, User, UserId, UserStore,
}; };
use context_menu::{ContextMenu, ContextMenuItem}; use context_menu::{ContextMenu, ContextMenuItem};
@ -9,7 +9,8 @@ use gpui::{
actions, actions,
elements::*, elements::*,
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle, AppContext, ClipboardItem, Entity, ModelHandle, MouseState, Task, View, ViewContext,
ViewHandle,
}; };
use picker::{Picker, PickerDelegate, PickerEvent}; use picker::{Picker, PickerDelegate, PickerEvent};
use std::sync::Arc; use std::sync::Arc;
@ -185,6 +186,81 @@ impl View for ChannelModal {
.into_any() .into_any()
} }
fn render_visibility(
channel_id: ChannelId,
visibility: ChannelVisibility,
theme: &theme::TabbedModal,
cx: &mut ViewContext<ChannelModal>,
) -> AnyElement<ChannelModal> {
enum TogglePublic {}
if visibility == ChannelVisibility::Members {
return Flex::row()
.with_child(
MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
let style = theme.visibility_toggle.style_for(state);
Label::new(format!("{}", "Public access: OFF"), style.text.clone())
.contained()
.with_style(style.container.clone())
})
.on_click(MouseButton::Left, move |_, this, cx| {
this.channel_store
.update(cx, |channel_store, cx| {
channel_store.set_channel_visibility(
channel_id,
ChannelVisibility::Public,
cx,
)
})
.detach_and_log_err(cx);
})
.with_cursor_style(CursorStyle::PointingHand),
)
.into_any();
}
Flex::row()
.with_child(
MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
let style = theme.visibility_toggle.style_for(state);
Label::new(format!("{}", "Public access: ON"), style.text.clone())
.contained()
.with_style(style.container.clone())
})
.on_click(MouseButton::Left, move |_, this, cx| {
this.channel_store
.update(cx, |channel_store, cx| {
channel_store.set_channel_visibility(
channel_id,
ChannelVisibility::Members,
cx,
)
})
.detach_and_log_err(cx);
})
.with_cursor_style(CursorStyle::PointingHand),
)
.with_spacing(14.0)
.with_child(
MouseEventHandler::new::<TogglePublic, _>(1, cx, move |state, _| {
let style = theme.channel_link.style_for(state);
Label::new(format!("{}", "copy link"), style.text.clone())
.contained()
.with_style(style.container.clone())
})
.on_click(MouseButton::Left, move |_, this, cx| {
if let Some(channel) =
this.channel_store.read(cx).channel_for_id(channel_id)
{
let item = ClipboardItem::new(channel.link());
cx.write_to_clipboard(item);
}
})
.with_cursor_style(CursorStyle::PointingHand),
)
.into_any()
}
Flex::column() Flex::column()
.with_child( .with_child(
Flex::column() Flex::column()
@ -193,6 +269,7 @@ impl View for ChannelModal {
.contained() .contained()
.with_style(theme.title.container.clone()), .with_style(theme.title.container.clone()),
) )
.with_child(render_visibility(channel.id, channel.visibility, theme, cx))
.with_child(Flex::row().with_children([ .with_child(Flex::row().with_children([
render_mode_button::<InviteMembers>( render_mode_button::<InviteMembers>(
Mode::InviteMembers, Mode::InviteMembers,

View file

@ -170,7 +170,8 @@ message Envelope {
LinkChannel link_channel = 140; LinkChannel link_channel = 140;
UnlinkChannel unlink_channel = 141; UnlinkChannel unlink_channel = 141;
MoveChannel move_channel = 142; // current max: 145 MoveChannel move_channel = 142;
SetChannelVisibility set_channel_visibility = 146; // current max: 146
} }
} }
@ -1049,6 +1050,11 @@ message SetChannelMemberRole {
ChannelRole role = 3; ChannelRole role = 3;
} }
message SetChannelVisibility {
uint64 channel_id = 1;
ChannelVisibility visibility = 2;
}
message RenameChannel { message RenameChannel {
uint64 channel_id = 1; uint64 channel_id = 1;
string name = 2; string name = 2;
@ -1542,7 +1548,7 @@ message Nonce {
enum ChannelVisibility { enum ChannelVisibility {
Public = 0; Public = 0;
ChannelMembers = 1; Members = 1;
} }
message Channel { message Channel {

View file

@ -231,6 +231,7 @@ messages!(
(RenameChannel, Foreground), (RenameChannel, Foreground),
(RenameChannelResponse, Foreground), (RenameChannelResponse, Foreground),
(SetChannelMemberRole, Foreground), (SetChannelMemberRole, Foreground),
(SetChannelVisibility, Foreground),
(SearchProject, Background), (SearchProject, Background),
(SearchProjectResponse, Background), (SearchProjectResponse, Background),
(ShareProject, Foreground), (ShareProject, Foreground),
@ -327,6 +328,7 @@ request_messages!(
(RespondToContactRequest, Ack), (RespondToContactRequest, Ack),
(RespondToChannelInvite, Ack), (RespondToChannelInvite, Ack),
(SetChannelMemberRole, Ack), (SetChannelMemberRole, Ack),
(SetChannelVisibility, Ack),
(SendChannelMessage, SendChannelMessageResponse), (SendChannelMessage, SendChannelMessageResponse),
(GetChannelMessages, GetChannelMessagesResponse), (GetChannelMessages, GetChannelMessagesResponse),
(GetChannelMembers, GetChannelMembersResponse), (GetChannelMembers, GetChannelMembersResponse),

View file

@ -286,6 +286,8 @@ pub struct TabbedModal {
pub header: ContainerStyle, pub header: ContainerStyle,
pub body: ContainerStyle, pub body: ContainerStyle,
pub title: ContainedText, pub title: ContainedText,
pub visibility_toggle: Interactive<ContainedText>,
pub channel_link: Interactive<ContainedText>,
pub picker: Picker, pub picker: Picker,
pub max_height: f32, pub max_height: f32,
pub max_width: f32, pub max_width: f32,

View file

@ -1,10 +1,11 @@
import { useTheme } from "../theme" import { StyleSet, StyleSets, Styles, useTheme } from "../theme"
import { background, border, foreground, text } from "./components" import { background, border, foreground, text } from "./components"
import picker from "./picker" import picker from "./picker"
import { input } from "../component/input" import { input } from "../component/input"
import contact_finder from "./contact_finder" import contact_finder from "./contact_finder"
import { tab } from "../component/tab" import { tab } from "../component/tab"
import { icon_button } from "../component/icon_button" import { icon_button } from "../component/icon_button"
import { interactive } from "../element/interactive"
export default function channel_modal(): any { export default function channel_modal(): any {
const theme = useTheme() const theme = useTheme()
@ -27,6 +28,24 @@ export default function channel_modal(): any {
const picker_input = input() const picker_input = input()
const interactive_text = (styleset: StyleSets) =>
interactive({
base: {
padding: {
left: 8,
top: 8
},
...text(theme.middle, "sans", styleset, "default"),
}, state: {
hovered: {
...text(theme.middle, "sans", styleset, "hovered"),
},
clicked: {
...text(theme.middle, "sans", styleset, "active"),
}
}
});
const member_icon_style = icon_button({ const member_icon_style = icon_button({
variant: "ghost", variant: "ghost",
size: "sm", size: "sm",
@ -88,6 +107,8 @@ export default function channel_modal(): any {
left: BUTTON_OFFSET, left: BUTTON_OFFSET,
}, },
}, },
visibility_toggle: interactive_text("base"),
channel_link: interactive_text("accent"),
picker: { picker: {
empty_container: {}, empty_container: {},
item: { item: {