Render the DAG

This commit is contained in:
Mikayla 2023-09-08 18:47:59 -07:00
parent 3a62d2988a
commit 8222102d01
No known key found for this signature in database
4 changed files with 227 additions and 123 deletions

View file

@ -11,6 +11,7 @@ use std::{mem, sync::Arc, time::Duration};
use util::ResultExt; use util::ResultExt;
use self::channel_index::ChannelIndex; use self::channel_index::ChannelIndex;
pub use self::channel_index::ChannelPath;
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
@ -145,11 +146,13 @@ impl ChannelStore {
}) })
} }
pub fn channel_at_index(&self, ix: usize) -> Option<(usize, &Arc<Channel>)> { pub fn channel_at_index(&self, ix: usize) -> Option<(usize, &Arc<Channel>, &Arc<[ChannelId]>)> {
let path = self.channel_index.get(ix)?; let path = self.channel_index.get(ix)?;
let id = path.last().unwrap(); let id = path.last().unwrap();
let channel = self.channel_for_id(*id).unwrap(); let channel = self.channel_for_id(*id).unwrap();
Some((path.len() - 1, channel))
Some((path.len() - 1, channel, path))
} }
pub fn channel_invitations(&self) -> &[Arc<Channel>] { pub fn channel_invitations(&self) -> &[Arc<Channel>] {
@ -734,12 +737,15 @@ impl ChannelStore {
} }
} }
self.channel_index.insert_channels(payload.channels); let mut channel_index = self.channel_index.start_upsert();
for channel in payload.channels {
channel_index.upsert(channel)
}
} }
for edge in payload.delete_channel_edge { for edge in payload.delete_channel_edge {
self.channel_index self.channel_index
.remove_edge(edge.parent_id, edge.channel_id); .delete_edge(edge.parent_id, edge.channel_id);
} }
for permission in payload.channel_permissions { for permission in payload.channel_permissions {

View file

@ -1,11 +1,11 @@
use std::{ops::{Deref, DerefMut}, sync::Arc}; use std::{ops::Deref, sync::Arc};
use collections::HashMap; use collections::HashMap;
use rpc::proto; use rpc::proto;
use crate::{ChannelId, Channel}; use crate::{ChannelId, Channel};
pub type ChannelPath = Vec<ChannelId>; pub type ChannelPath = Arc<[ChannelId]>;
pub type ChannelsById = HashMap<ChannelId, Arc<Channel>>; pub type ChannelsById = HashMap<ChannelId, Arc<Channel>>;
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -20,33 +20,6 @@ impl ChannelIndex {
&self.channels_by_id &self.channels_by_id
} }
/// Insert or update all of the given channels into the index
pub fn insert_channels(&mut self, channels: Vec<proto::Channel>) {
let mut insert = self.insert();
for channel_proto in channels {
if let Some(existing_channel) = insert.channels_by_id.get_mut(&channel_proto.id) {
Arc::make_mut(existing_channel).name = channel_proto.name;
if let Some(parent_id) = channel_proto.parent_id {
insert.insert_edge(parent_id, channel_proto.id)
}
} else {
let channel = Arc::new(Channel {
id: channel_proto.id,
name: channel_proto.name,
});
insert.channels_by_id.insert(channel.id, channel.clone());
if let Some(parent_id) = channel_proto.parent_id {
insert.insert_edge(parent_id, channel.id);
} else {
insert.insert_root(channel.id);
}
}
}
}
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.paths.clear(); self.paths.clear();
self.channels_by_id.clear(); self.channels_by_id.clear();
@ -54,7 +27,7 @@ impl ChannelIndex {
/// Remove the given edge from this index. This will not remove the channel /// Remove the given edge from this index. This will not remove the channel
/// and may result in dangling channels. /// and may result in dangling channels.
pub fn remove_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) { pub fn delete_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) {
self.paths.retain(|path| { self.paths.retain(|path| {
!path !path
.windows(2) .windows(2)
@ -68,8 +41,9 @@ impl ChannelIndex {
self.paths.retain(|channel_path| !channel_path.iter().any(|channel_id| {channels.contains(channel_id)})) self.paths.retain(|channel_path| !channel_path.iter().any(|channel_id| {channels.contains(channel_id)}))
} }
fn insert(& mut self) -> ChannelPathsInsertGuard { /// Upsert one or more channels into this index.
ChannelPathsInsertGuard { pub fn start_upsert(& mut self) -> ChannelPathsUpsertGuard {
ChannelPathsUpsertGuard {
paths: &mut self.paths, paths: &mut self.paths,
channels_by_id: &mut self.channels_by_id, channels_by_id: &mut self.channels_by_id,
} }
@ -86,47 +60,54 @@ impl Deref for ChannelIndex {
/// A guard for ensuring that the paths index maintains its sort and uniqueness /// A guard for ensuring that the paths index maintains its sort and uniqueness
/// invariants after a series of insertions /// invariants after a series of insertions
struct ChannelPathsInsertGuard<'a> { pub struct ChannelPathsUpsertGuard<'a> {
paths: &'a mut Vec<ChannelPath>, paths: &'a mut Vec<ChannelPath>,
channels_by_id: &'a mut ChannelsById, channels_by_id: &'a mut ChannelsById,
} }
impl Deref for ChannelPathsInsertGuard<'_> { impl<'a> ChannelPathsUpsertGuard<'a> {
type Target = ChannelsById; pub fn upsert(&mut self, channel_proto: proto::Channel) {
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
Arc::make_mut(existing_channel).name = channel_proto.name;
fn deref(&self) -> &Self::Target { if let Some(parent_id) = channel_proto.parent_id {
&self.channels_by_id self.insert_edge(parent_id, channel_proto.id)
}
} else {
let channel = Arc::new(Channel {
id: channel_proto.id,
name: channel_proto.name,
});
self.channels_by_id.insert(channel.id, channel.clone());
if let Some(parent_id) = channel_proto.parent_id {
self.insert_edge(parent_id, channel.id);
} else {
self.insert_root(channel.id);
}
}
} }
}
impl DerefMut for ChannelPathsInsertGuard<'_> { fn insert_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.channels_by_id
}
}
impl<'a> ChannelPathsInsertGuard<'a> {
pub fn insert_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) {
let mut ix = 0; let mut ix = 0;
while ix < self.paths.len() { while ix < self.paths.len() {
let path = &self.paths[ix]; let path = &self.paths[ix];
if path.ends_with(&[parent_id]) { if path.ends_with(&[parent_id]) {
let mut new_path = path.clone(); let mut new_path = path.to_vec();
new_path.push(channel_id); new_path.push(channel_id);
self.paths.insert(ix + 1, new_path); self.paths.insert(ix + 1, new_path.into());
ix += 1; ix += 1;
} }
ix += 1; ix += 1;
} }
} }
pub fn insert_root(&mut self, channel_id: ChannelId) { fn insert_root(&mut self, channel_id: ChannelId) {
self.paths.push(vec![channel_id]); self.paths.push(Arc::from([channel_id]));
} }
} }
impl<'a> Drop for ChannelPathsInsertGuard<'a> { impl<'a> Drop for ChannelPathsUpsertGuard<'a> {
fn drop(&mut self) { fn drop(&mut self) {
self.paths.sort_by(|a, b| { self.paths.sort_by(|a, b| {
let a = channel_path_sorting_key(a, &self.channels_by_id); let a = channel_path_sorting_key(a, &self.channels_by_id);

View file

@ -9,7 +9,7 @@ use crate::{
}; };
use anyhow::Result; use anyhow::Result;
use call::ActiveCall; use call::ActiveCall;
use channel::{Channel, ChannelEvent, ChannelId, ChannelStore}; use channel::{Channel, ChannelEvent, ChannelId, ChannelStore, ChannelPath};
use channel_modal::ChannelModal; use channel_modal::ChannelModal;
use client::{proto::PeerId, Client, Contact, User, UserStore}; use client::{proto::PeerId, Client, Contact, User, UserStore};
use contact_finder::ContactFinder; use contact_finder::ContactFinder;
@ -40,7 +40,7 @@ use menu::{Confirm, SelectNext, SelectPrev};
use project::{Fs, Project}; use project::{Fs, Project};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::SettingsStore; use settings::SettingsStore;
use std::{borrow::Cow, mem, sync::Arc}; use std::{borrow::Cow, mem, sync::Arc, hash::Hash};
use theme::{components::ComponentExt, IconButton}; use theme::{components::ComponentExt, IconButton};
use util::{iife, ResultExt, TryFutureExt}; use util::{iife, ResultExt, TryFutureExt};
use workspace::{ use workspace::{
@ -51,32 +51,32 @@ use workspace::{
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct RemoveChannel { struct RemoveChannel {
channel_id: u64, channel_id: ChannelId,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct ToggleCollapse { struct ToggleCollapse {
channel_id: u64, location: ChannelLocation<'static>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct NewChannel { struct NewChannel {
channel_id: u64, location: ChannelLocation<'static>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct InviteMembers { struct InviteMembers {
channel_id: u64, channel_id: ChannelId,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct ManageMembers { struct ManageMembers {
channel_id: u64, channel_id: ChannelId,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct RenameChannel { struct RenameChannel {
channel_id: u64, location: ChannelLocation<'static>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -89,6 +89,26 @@ pub struct JoinChannelCall {
pub channel_id: u64, pub channel_id: u64,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct OpenChannelBuffer {
channel_id: ChannelId,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct CopyChannel {
channel_id: ChannelId,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct CutChannel {
channel_id: ChannelId,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct PasteChannel {
channel_id: ChannelId,
}
actions!( actions!(
collab_panel, collab_panel,
[ [
@ -111,12 +131,35 @@ impl_actions!(
ToggleCollapse, ToggleCollapse,
OpenChannelNotes, OpenChannelNotes,
JoinChannelCall, JoinChannelCall,
OpenChannelBuffer,
CopyChannel,
CutChannel,
PasteChannel,
] ]
); );
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ChannelLocation<'a> {
channel: ChannelId,
parent: Cow<'a, ChannelPath>,
}
impl From<(ChannelId, ChannelPath)> for ChannelLocation<'static> {
fn from(value: (ChannelId, ChannelPath)) -> Self {
ChannelLocation { channel: value.0, parent: Cow::Owned(value.1) }
}
}
impl<'a> From<(ChannelId, &'a ChannelPath)> for ChannelLocation<'a> {
fn from(value: (ChannelId, &'a ChannelPath)) -> Self {
ChannelLocation { channel: value.0, parent: Cow::Borrowed(value.1) }
}
}
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
settings::register::<panel_settings::CollaborationPanelSettings>(cx);
contact_finder::init(cx); contact_finder::init(cx);
channel_modal::init(cx); channel_modal::init(cx);
channel_view::init(cx); channel_view::init(cx);
@ -137,16 +180,37 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(CollabPanel::collapse_selected_channel); 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_notes); cx.add_action(CollabPanel::open_channel_notes);
cx.add_action(CollabPanel::open_channel_buffer);
cx.add_action(|panel: &mut CollabPanel, action: &CopyChannel, _: &mut ViewContext<CollabPanel>| {
panel.copy = Some(ChannelCopy::Copy(action.channel_id));
});
cx.add_action(|panel: &mut CollabPanel, action: &CutChannel, _: &mut ViewContext<CollabPanel>| {
// panel.copy = Some(ChannelCopy::Cut(action.channel_id));
});
cx.add_action(|panel: &mut CollabPanel, action: &PasteChannel, cx: &mut ViewContext<CollabPanel>| {
if let Some(copy) = &panel.copy {
match copy {
ChannelCopy::Cut {..} => todo!(),
ChannelCopy::Copy(channel) => panel.channel_store.update(cx, |channel_store, cx| {
channel_store.move_channel(*channel, None, Some(action.channel_id), cx).detach_and_log_err(cx)
}),
}
}
});
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ChannelEditingState { pub enum ChannelEditingState {
Create { Create {
parent_id: Option<u64>, location: Option<ChannelLocation<'static>>,
pending_name: Option<String>, pending_name: Option<String>,
}, },
Rename { Rename {
channel_id: u64, location: ChannelLocation<'static>,
pending_name: Option<String>, pending_name: Option<String>,
}, },
} }
@ -160,10 +224,19 @@ impl ChannelEditingState {
} }
} }
enum ChannelCopy {
Cut {
channel_id: u64,
parent_id: Option<u64>,
},
Copy(u64),
}
pub struct CollabPanel { pub struct CollabPanel {
width: Option<f32>, width: Option<f32>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
has_focus: bool, has_focus: bool,
copy: Option<ChannelCopy>,
pending_serialization: Task<Option<()>>, pending_serialization: Task<Option<()>>,
context_menu: ViewHandle<ContextMenu>, context_menu: ViewHandle<ContextMenu>,
filter_editor: ViewHandle<Editor>, filter_editor: ViewHandle<Editor>,
@ -179,7 +252,7 @@ pub struct CollabPanel {
list_state: ListState<Self>, list_state: ListState<Self>,
subscriptions: Vec<Subscription>, subscriptions: Vec<Subscription>,
collapsed_sections: Vec<Section>, collapsed_sections: Vec<Section>,
collapsed_channels: Vec<ChannelId>, collapsed_channels: Vec<ChannelLocation<'static>>,
workspace: WeakViewHandle<Workspace>, workspace: WeakViewHandle<Workspace>,
context_menu_on_selected: bool, context_menu_on_selected: bool,
} }
@ -187,7 +260,7 @@ pub struct CollabPanel {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct SerializedCollabPanel { struct SerializedCollabPanel {
width: Option<f32>, width: Option<f32>,
collapsed_channels: Option<Vec<ChannelId>>, collapsed_channels: Option<Vec<ChannelLocation<'static>>>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -231,6 +304,7 @@ enum ListEntry {
Channel { Channel {
channel: Arc<Channel>, channel: Arc<Channel>,
depth: usize, depth: usize,
path: Arc<[ChannelId]>,
}, },
ChannelNotes { ChannelNotes {
channel_id: ChannelId, channel_id: ChannelId,
@ -348,10 +422,11 @@ impl CollabPanel {
cx, cx,
) )
} }
ListEntry::Channel { channel, depth } => { ListEntry::Channel { channel, depth, path } => {
let channel_row = this.render_channel( let channel_row = this.render_channel(
&*channel, &*channel,
*depth, *depth,
path.to_owned(),
&theme.collab_panel, &theme.collab_panel,
is_selected, is_selected,
cx, cx,
@ -420,6 +495,7 @@ impl CollabPanel {
let mut this = Self { let mut this = Self {
width: None, width: None,
has_focus: false, has_focus: false,
copy: None,
fs: workspace.app_state().fs.clone(), fs: workspace.app_state().fs.clone(),
pending_serialization: Task::ready(None), pending_serialization: Task::ready(None),
context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
@ -700,7 +776,7 @@ impl CollabPanel {
if matches!( if matches!(
state, state,
ChannelEditingState::Create { ChannelEditingState::Create {
parent_id: None, location: None,
.. ..
} }
) { ) {
@ -709,16 +785,18 @@ impl CollabPanel {
} }
let mut collapse_depth = None; let mut collapse_depth = None;
for mat in matches { for mat in matches {
let (depth, channel) = let (depth, channel, path) =
channel_store.channel_at_index(mat.candidate_id).unwrap(); channel_store.channel_at_index(mat.candidate_id).unwrap();
if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { let location: ChannelLocation<'_> = (channel.id, path).into();
if collapse_depth.is_none() && self.is_channel_collapsed(&location) {
collapse_depth = Some(depth); collapse_depth = Some(depth);
} else if let Some(collapsed_depth) = collapse_depth { } else if let Some(collapsed_depth) = collapse_depth {
if depth > collapsed_depth { if depth > collapsed_depth {
continue; continue;
} }
if self.is_channel_collapsed(channel.id) { if self.is_channel_collapsed(&location) {
collapse_depth = Some(depth); collapse_depth = Some(depth);
} else { } else {
collapse_depth = None; collapse_depth = None;
@ -726,18 +804,19 @@ impl CollabPanel {
} }
match &self.channel_editing_state { match &self.channel_editing_state {
Some(ChannelEditingState::Create { parent_id, .. }) Some(ChannelEditingState::Create { location: parent_id, .. })
if *parent_id == Some(channel.id) => if *parent_id == Some(location) =>
{ {
self.entries.push(ListEntry::Channel { self.entries.push(ListEntry::Channel {
channel: channel.clone(), channel: channel.clone(),
depth, depth,
path: path.clone(),
}); });
self.entries self.entries
.push(ListEntry::ChannelEditor { depth: depth + 1 }); .push(ListEntry::ChannelEditor { depth: depth + 1 });
} }
Some(ChannelEditingState::Rename { channel_id, .. }) Some(ChannelEditingState::Rename { location, .. })
if *channel_id == channel.id => if location.channel == channel.id && location.parent == Cow::Borrowed(path) =>
{ {
self.entries.push(ListEntry::ChannelEditor { depth }); self.entries.push(ListEntry::ChannelEditor { depth });
} }
@ -745,6 +824,7 @@ impl CollabPanel {
self.entries.push(ListEntry::Channel { self.entries.push(ListEntry::Channel {
channel: channel.clone(), channel: channel.clone(),
depth, depth,
path: path.clone()
}); });
} }
} }
@ -1546,14 +1626,21 @@ impl CollabPanel {
&self, &self,
channel: &Channel, channel: &Channel,
depth: usize, depth: usize,
path: ChannelPath,
theme: &theme::CollabPanel, theme: &theme::CollabPanel,
is_selected: bool, is_selected: bool,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> AnyElement<Self> { ) -> AnyElement<Self> {
let channel_id = channel.id; let channel_id = channel.id;
let has_children = self.channel_store.read(cx).has_children(channel_id); let has_children = self.channel_store.read(cx).has_children(channel_id);
let disclosed =
has_children.then(|| !self.collapsed_channels.binary_search(&channel_id).is_ok()); let disclosed = {
let location = ChannelLocation {
channel: channel_id,
parent: Cow::Borrowed(&path),
};
has_children.then(|| !self.collapsed_channels.binary_search(&location).is_ok())
};
let is_active = iife!({ let is_active = iife!({
let call_channel = ActiveCall::global(cx) let call_channel = ActiveCall::global(cx)
@ -1569,7 +1656,7 @@ impl CollabPanel {
enum ChannelCall {} enum ChannelCall {}
MouseEventHandler::new::<Channel, _>(channel.id as usize, cx, |state, cx| { MouseEventHandler::new::<Channel, _>(id(&path) as usize, cx, |state, cx| {
let row_hovered = state.hovered(); let row_hovered = state.hovered();
Flex::<Self>::row() Flex::<Self>::row()
@ -1637,8 +1724,8 @@ impl CollabPanel {
) )
.align_children_center() .align_children_center()
.styleable_component() .styleable_component()
.disclosable(disclosed, Box::new(ToggleCollapse { channel_id })) .disclosable(disclosed, Box::new(ToggleCollapse { location: (channel_id, path.clone()).into() }))
.with_id(channel_id as usize) .with_id(id(&path) as usize)
.with_style(theme.disclosure.clone()) .with_style(theme.disclosure.clone())
.element() .element()
.constrained() .constrained()
@ -1654,7 +1741,7 @@ impl CollabPanel {
this.join_channel_chat(channel_id, cx); this.join_channel_chat(channel_id, cx);
}) })
.on_click(MouseButton::Right, move |e, this, cx| { .on_click(MouseButton::Right, move |e, this, cx| {
this.deploy_channel_context_menu(Some(e.position), channel_id, cx); this.deploy_channel_context_menu(Some(e.position), &(channel_id, path.clone()).into(), cx);
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.into_any() .into_any()
@ -1901,7 +1988,7 @@ impl CollabPanel {
fn deploy_channel_context_menu( fn deploy_channel_context_menu(
&mut self, &mut self,
position: Option<Vector2F>, position: Option<Vector2F>,
channel_id: u64, location: &ChannelLocation<'static>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.context_menu_on_selected = position.is_none(); self.context_menu_on_selected = position.is_none();
@ -1913,27 +2000,29 @@ impl CollabPanel {
OverlayPositionMode::Window OverlayPositionMode::Window
}); });
let expand_action_name = if self.is_channel_collapsed(channel_id) { let expand_action_name = if self.is_channel_collapsed(&location) {
"Expand Subchannels" "Expand Subchannels"
} else { } else {
"Collapse Subchannels" "Collapse Subchannels"
}; };
let mut items = vec![ let mut items = vec![
ContextMenuItem::action(expand_action_name, ToggleCollapse { channel_id }), ContextMenuItem::action(expand_action_name, ToggleCollapse { location: location.clone() }),
ContextMenuItem::action("Open Notes", OpenChannelNotes { channel_id }), ContextMenuItem::action("Open Notes", OpenChannelBuffer { channel_id: location.channel }),
]; ];
if self.channel_store.read(cx).is_user_admin(channel_id) { if self.channel_store.read(cx).is_user_admin(location.channel) {
items.extend([ items.extend([
ContextMenuItem::Separator, ContextMenuItem::Separator,
ContextMenuItem::action("New Subchannel", NewChannel { channel_id }), ContextMenuItem::action("New Subchannel", NewChannel { location: location.clone() }),
ContextMenuItem::action("Rename", RenameChannel { channel_id }), ContextMenuItem::action("Rename", RenameChannel { location: location.clone() }),
ContextMenuItem::action("Copy", CopyChannel { channel_id: location.channel }),
ContextMenuItem::action("Paste", PasteChannel { channel_id: location.channel }),
ContextMenuItem::Separator, ContextMenuItem::Separator,
ContextMenuItem::action("Invite Members", InviteMembers { channel_id }), ContextMenuItem::action("Invite Members", InviteMembers { channel_id: location.channel }),
ContextMenuItem::action("Manage Members", ManageMembers { channel_id }), ContextMenuItem::action("Manage Members", ManageMembers { channel_id: location.channel }),
ContextMenuItem::Separator, ContextMenuItem::Separator,
ContextMenuItem::action("Delete", RemoveChannel { channel_id }), ContextMenuItem::action("Delete", RemoveChannel { channel_id: location.channel }),
]); ]);
} }
@ -2059,7 +2148,7 @@ impl CollabPanel {
if let Some(editing_state) = &mut self.channel_editing_state { if let Some(editing_state) = &mut self.channel_editing_state {
match editing_state { match editing_state {
ChannelEditingState::Create { ChannelEditingState::Create {
parent_id, location,
pending_name, pending_name,
.. ..
} => { } => {
@ -2072,13 +2161,13 @@ impl CollabPanel {
self.channel_store self.channel_store
.update(cx, |channel_store, cx| { .update(cx, |channel_store, cx| {
channel_store.create_channel(&channel_name, *parent_id, cx) channel_store.create_channel(&channel_name, location.as_ref().map(|location| location.channel), cx)
}) })
.detach(); .detach();
cx.notify(); cx.notify();
} }
ChannelEditingState::Rename { ChannelEditingState::Rename {
channel_id, location,
pending_name, pending_name,
} => { } => {
if pending_name.is_some() { if pending_name.is_some() {
@ -2089,7 +2178,7 @@ impl CollabPanel {
self.channel_store self.channel_store
.update(cx, |channel_store, cx| { .update(cx, |channel_store, cx| {
channel_store.rename(*channel_id, &channel_name, cx) channel_store.rename(location.channel, &channel_name, cx)
}) })
.detach(); .detach();
cx.notify(); cx.notify();
@ -2116,38 +2205,42 @@ impl CollabPanel {
_: &CollapseSelectedChannel, _: &CollapseSelectedChannel,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else { let Some((channel_id, path)) = self.selected_channel().map(|(channel, parent)| (channel.id, parent)) else {
return; return;
}; };
if self.is_channel_collapsed(channel_id) { let path = path.to_owned();
if self.is_channel_collapsed(&(channel_id, path.clone()).into()) {
return; return;
} }
self.toggle_channel_collapsed(&ToggleCollapse { channel_id }, cx) self.toggle_channel_collapsed(&ToggleCollapse { location: (channel_id, path).into() }, cx)
} }
fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) { fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else { let Some((channel_id, path)) = self.selected_channel().map(|(channel, parent)| (channel.id, parent)) else {
return; return;
}; };
if !self.is_channel_collapsed(channel_id) { let path = path.to_owned();
if !self.is_channel_collapsed(&(channel_id, path.clone()).into()) {
return; return;
} }
self.toggle_channel_collapsed(&ToggleCollapse { channel_id }, cx) self.toggle_channel_collapsed(&ToggleCollapse { location: (channel_id, path).into() }, cx)
} }
fn toggle_channel_collapsed(&mut self, action: &ToggleCollapse, cx: &mut ViewContext<Self>) { fn toggle_channel_collapsed(&mut self, action: &ToggleCollapse, cx: &mut ViewContext<Self>) {
let channel_id = action.channel_id; let location = action.location.clone();
match self.collapsed_channels.binary_search(&channel_id) { match self.collapsed_channels.binary_search(&location) {
Ok(ix) => { Ok(ix) => {
self.collapsed_channels.remove(ix); self.collapsed_channels.remove(ix);
} }
Err(ix) => { Err(ix) => {
self.collapsed_channels.insert(ix, channel_id); self.collapsed_channels.insert(ix, location);
} }
}; };
self.serialize(cx); self.serialize(cx);
@ -2156,8 +2249,8 @@ impl CollabPanel {
cx.focus_self(); cx.focus_self();
} }
fn is_channel_collapsed(&self, channel: ChannelId) -> bool { fn is_channel_collapsed(&self, location: &ChannelLocation) -> bool {
self.collapsed_channels.binary_search(&channel).is_ok() self.collapsed_channels.binary_search(location).is_ok()
} }
fn leave_call(cx: &mut ViewContext<Self>) { fn leave_call(cx: &mut ViewContext<Self>) {
@ -2182,7 +2275,7 @@ impl CollabPanel {
fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) { fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
self.channel_editing_state = Some(ChannelEditingState::Create { self.channel_editing_state = Some(ChannelEditingState::Create {
parent_id: None, location: None,
pending_name: None, pending_name: None,
}); });
self.update_entries(false, cx); self.update_entries(false, cx);
@ -2200,9 +2293,9 @@ impl CollabPanel {
fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) { fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
self.collapsed_channels self.collapsed_channels
.retain(|&channel| channel != action.channel_id); .retain(|channel| *channel != action.location);
self.channel_editing_state = Some(ChannelEditingState::Create { self.channel_editing_state = Some(ChannelEditingState::Create {
parent_id: Some(action.channel_id), location: Some(action.location.to_owned()),
pending_name: None, pending_name: None,
}); });
self.update_entries(false, cx); self.update_entries(false, cx);
@ -2220,16 +2313,16 @@ impl CollabPanel {
} }
fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) { fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
if let Some(channel) = self.selected_channel() { if let Some((channel, _)) = self.selected_channel() {
self.remove_channel(channel.id, cx) self.remove_channel(channel.id, cx)
} }
} }
fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) { fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
if let Some(channel) = self.selected_channel() { if let Some((channel, parent)) = self.selected_channel() {
self.rename_channel( self.rename_channel(
&RenameChannel { &RenameChannel {
channel_id: channel.id, location: (channel.id, parent.to_owned()).into(),
}, },
cx, cx,
); );
@ -2238,12 +2331,12 @@ impl CollabPanel {
fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) { fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
let channel_store = self.channel_store.read(cx); let channel_store = self.channel_store.read(cx);
if !channel_store.is_user_admin(action.channel_id) { if !channel_store.is_user_admin(action.location.channel) {
return; return;
} }
if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() { if let Some(channel) = channel_store.channel_for_id(action.location.channel).cloned() {
self.channel_editing_state = Some(ChannelEditingState::Rename { self.channel_editing_state = Some(ChannelEditingState::Rename {
channel_id: action.channel_id, location: action.location.to_owned(),
pending_name: None, pending_name: None,
}); });
self.channel_name_editor.update(cx, |editor, cx| { self.channel_name_editor.update(cx, |editor, cx| {
@ -2263,18 +2356,18 @@ impl CollabPanel {
} }
fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) { fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
let Some(channel) = self.selected_channel() else { let Some((channel, path)) = self.selected_channel() else {
return; return;
}; };
self.deploy_channel_context_menu(None, channel.id, cx); self.deploy_channel_context_menu(None, &(channel.id, path.to_owned()).into(), cx);
} }
fn selected_channel(&self) -> Option<&Arc<Channel>> { fn selected_channel(&self) -> Option<(&Arc<Channel>, &ChannelPath)> {
self.selection self.selection
.and_then(|ix| self.entries.get(ix)) .and_then(|ix| self.entries.get(ix))
.and_then(|entry| match entry { .and_then(|entry| match entry {
ListEntry::Channel { channel, .. } => Some(channel), ListEntry::Channel { channel, path: parent, .. } => Some((channel, parent)),
_ => None, _ => None,
}) })
} }
@ -2657,13 +2750,15 @@ impl PartialEq for ListEntry {
ListEntry::Channel { ListEntry::Channel {
channel: channel_1, channel: channel_1,
depth: depth_1, depth: depth_1,
path: parent_1,
} => { } => {
if let ListEntry::Channel { if let ListEntry::Channel {
channel: channel_2, channel: channel_2,
depth: depth_2, depth: depth_2,
path: parent_2,
} = other } = other
{ {
return channel_1.id == channel_2.id && depth_1 == depth_2; return channel_1.id == channel_2.id && depth_1 == depth_2 && parent_1 == parent_2;
} }
} }
ListEntry::ChannelNotes { channel_id } => { ListEntry::ChannelNotes { channel_id } => {
@ -2726,3 +2821,26 @@ fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Elemen
.contained() .contained()
.with_style(style.container) .with_style(style.container)
} }
/// Hash a channel path to a u64, for use as a mouse id
/// Based on the FowlerNollVo hash:
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
fn id(path: &[ChannelId]) -> u64 {
// I probably should have done this, but I didn't
// let hasher = DefaultHasher::new();
// let path = path.hash(&mut hasher);
// let x = hasher.finish();
const OFFSET: u64 = 14695981039346656037;
const PRIME: u64 = 1099511628211;
let mut hash = OFFSET;
for id in path.iter() {
for id in id.to_ne_bytes() {
hash = hash ^ (id as u64);
hash = (hash as u128 * PRIME as u128) as u64;
}
}
hash
}

View file

@ -328,7 +328,6 @@ request_messages!(
(GetChannelMessages, GetChannelMessagesResponse), (GetChannelMessages, GetChannelMessagesResponse),
(GetChannelMembers, GetChannelMembersResponse), (GetChannelMembers, GetChannelMembersResponse),
(JoinChannel, JoinRoomResponse), (JoinChannel, JoinRoomResponse),
(RemoveChannel, Ack),
(RemoveChannelMessage, Ack), (RemoveChannelMessage, Ack),
(DeleteChannel, Ack), (DeleteChannel, Ack),
(RenameProjectEntry, ProjectEntryResponse), (RenameProjectEntry, ProjectEntryResponse),