Simplify path representation in collab panel
Optimize set representation in collab
This commit is contained in:
parent
5400605483
commit
52057c5619
6 changed files with 163 additions and 175 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1505,6 +1505,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"sha-1 0.9.8",
|
"sha-1 0.9.8",
|
||||||
|
"smallvec",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"text",
|
"text",
|
||||||
"theme",
|
"theme",
|
||||||
|
|
|
@ -3,12 +3,12 @@ mod channel_index;
|
||||||
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat};
|
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use client::{Client, Subscription, User, UserId, UserStore};
|
use client::{Client, Subscription, User, UserId, UserStore};
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map::{self, DefaultHasher}, HashMap, HashSet};
|
||||||
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::{proto, TypedEnvelope};
|
use rpc::{proto, TypedEnvelope};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::{mem, ops::Deref, sync::Arc, time::Duration};
|
use std::{mem, ops::Deref, sync::Arc, time::Duration, borrow::Cow, hash::{Hash, Hasher}};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
use self::channel_index::ChannelIndex;
|
use self::channel_index::ChannelIndex;
|
||||||
|
@ -43,26 +43,6 @@ pub struct Channel {
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
||||||
pub struct ChannelPath(Arc<[ChannelId]>);
|
pub struct ChannelPath(Arc<[ChannelId]>);
|
||||||
|
|
||||||
impl Deref for ChannelPath {
|
|
||||||
type Target = [ChannelId];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelPath {
|
|
||||||
pub fn parent_id(&self) -> Option<ChannelId> {
|
|
||||||
self.0.len().checked_sub(2).map(|i| self.0[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ChannelPath {
|
|
||||||
fn default() -> Self {
|
|
||||||
ChannelPath(Arc::from([]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChannelMembership {
|
pub struct ChannelMembership {
|
||||||
pub user: Arc<User>,
|
pub user: Arc<User>,
|
||||||
pub kind: proto::channel_member::Kind,
|
pub kind: proto::channel_member::Kind,
|
||||||
|
@ -860,3 +840,50 @@ impl ChannelStore {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for ChannelPath {
|
||||||
|
type Target = [ChannelId];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChannelPath {
|
||||||
|
pub fn new(path: Arc<[ChannelId]>) -> Self {
|
||||||
|
debug_assert!(path.len() >= 1);
|
||||||
|
Self(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent_id(&self) -> Option<ChannelId> {
|
||||||
|
self.0.len().checked_sub(2).map(|i| self.0[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel_id(&self) -> ChannelId {
|
||||||
|
self.0[self.0.len() - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unique_id(&self) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
self.0.deref().hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ChannelPath> for Cow<'static, ChannelPath> {
|
||||||
|
fn from(value: ChannelPath) -> Self {
|
||||||
|
Cow::Owned(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a ChannelPath> for Cow<'a, ChannelPath> {
|
||||||
|
fn from(value: &'a ChannelPath) -> Self {
|
||||||
|
Cow::Borrowed(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ChannelPath {
|
||||||
|
fn default() -> Self {
|
||||||
|
ChannelPath(Arc::from([]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ impl<'a> ChannelPathsEditGuard<'a> {
|
||||||
if path.ends_with(&[parent_id]) {
|
if path.ends_with(&[parent_id]) {
|
||||||
let mut new_path = path.to_vec();
|
let mut new_path = path.to_vec();
|
||||||
new_path.push(channel_id);
|
new_path.push(channel_id);
|
||||||
self.paths.insert(ix + 1, ChannelPath(new_path.into()));
|
self.paths.insert(ix + 1, ChannelPath::new(new_path.into()));
|
||||||
ix += 2;
|
ix += 2;
|
||||||
} else if path.get(0) == Some(&channel_id) {
|
} else if path.get(0) == Some(&channel_id) {
|
||||||
// Clear out any paths that have this chahnnel as their root
|
// Clear out any paths that have this chahnnel as their root
|
||||||
|
@ -120,7 +120,7 @@ impl<'a> ChannelPathsEditGuard<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_root(&mut self, channel_id: ChannelId) {
|
fn insert_root(&mut self, channel_id: ChannelId) {
|
||||||
self.paths.push(ChannelPath(Arc::from([channel_id])));
|
self.paths.push(ChannelPath::new(Arc::from([channel_id])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ prost.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
reqwest = { version = "0.11", features = ["json"], optional = true }
|
reqwest = { version = "0.11", features = ["json"], optional = true }
|
||||||
scrypt = "0.7"
|
scrypt = "0.7"
|
||||||
|
smallvec.workspace = true
|
||||||
# Remove fork dependency when a version with https://github.com/SeaQL/sea-orm/pull/1283 is released.
|
# Remove fork dependency when a version with https://github.com/SeaQL/sea-orm/pull/1283 is released.
|
||||||
sea-orm = { git = "https://github.com/zed-industries/sea-orm", rev = "18f4c691085712ad014a51792af75a9044bacee6", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls"] }
|
sea-orm = { git = "https://github.com/zed-industries/sea-orm", rev = "18f4c691085712ad014a51792af75a9044bacee6", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls"] }
|
||||||
sea-query = "0.27"
|
sea-query = "0.27"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
type ChannelDescendants = HashMap<ChannelId, HashSet<ChannelId>>;
|
type ChannelDescendants = HashMap<ChannelId, SmallSet<ChannelId>>;
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -150,7 +152,7 @@ impl Database {
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Delete any other paths that incldue this channel
|
// Delete any other paths that include this channel
|
||||||
let sql = r#"
|
let sql = r#"
|
||||||
DELETE FROM channel_paths
|
DELETE FROM channel_paths
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -351,7 +353,7 @@ impl Database {
|
||||||
let parents = parents_by_child_id.get(&row.id).unwrap();
|
let parents = parents_by_child_id.get(&row.id).unwrap();
|
||||||
if parents.len() > 0 {
|
if parents.len() > 0 {
|
||||||
let mut added_channel = false;
|
let mut added_channel = false;
|
||||||
for parent in parents {
|
for parent in parents.iter() {
|
||||||
// Trim out any dangling parent pointers.
|
// Trim out any dangling parent pointers.
|
||||||
// That the user doesn't have access to
|
// That the user doesn't have access to
|
||||||
if trim_dangling_parents {
|
if trim_dangling_parents {
|
||||||
|
@ -843,7 +845,7 @@ impl Database {
|
||||||
);
|
);
|
||||||
tx.execute(channel_paths_stmt).await?;
|
tx.execute(channel_paths_stmt).await?;
|
||||||
for (from_id, to_ids) in from_descendants.iter().filter(|(id, _)| id != &&channel) {
|
for (from_id, to_ids) in from_descendants.iter().filter(|(id, _)| id != &&channel) {
|
||||||
for to_id in to_ids {
|
for to_id in to_ids.iter() {
|
||||||
let channel_paths_stmt = Statement::from_sql_and_values(
|
let channel_paths_stmt = Statement::from_sql_and_values(
|
||||||
self.pool.get_database_backend(),
|
self.pool.get_database_backend(),
|
||||||
sql,
|
sql,
|
||||||
|
@ -979,3 +981,38 @@ impl Database {
|
||||||
enum QueryUserIds {
|
enum QueryUserIds {
|
||||||
UserId,
|
UserId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SmallSet<T>(SmallVec<[T; 1]>);
|
||||||
|
|
||||||
|
impl<T> Deref for SmallSet<T> {
|
||||||
|
type Target = [T];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for SmallSet<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(SmallVec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SmallSet<T> {
|
||||||
|
fn insert(&mut self, value: T) -> bool
|
||||||
|
where
|
||||||
|
T: Ord,
|
||||||
|
{
|
||||||
|
match self.binary_search(&value) {
|
||||||
|
Ok(_) => false,
|
||||||
|
Err(ix) => {
|
||||||
|
self.0.insert(ix, value);
|
||||||
|
true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.0.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -49,19 +49,24 @@ use workspace::{
|
||||||
Workspace,
|
Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
||||||
struct RemoveChannel {
|
|
||||||
channel_id: ChannelId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
struct ToggleCollapse {
|
struct ToggleCollapse {
|
||||||
location: ChannelLocation<'static>,
|
location: ChannelPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
struct NewChannel {
|
struct NewChannel {
|
||||||
location: ChannelLocation<'static>,
|
location: ChannelPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
struct RenameChannel {
|
||||||
|
location: ChannelPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
struct RemoveChannel {
|
||||||
|
channel_id: ChannelId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
@ -74,11 +79,6 @@ struct ManageMembers {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
||||||
struct RenameChannel {
|
|
||||||
location: ChannelLocation<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct OpenChannelNotes {
|
pub struct OpenChannelNotes {
|
||||||
pub channel_id: u64,
|
pub channel_id: u64,
|
||||||
|
@ -148,30 +148,6 @@ impl_actions!(
|
||||||
|
|
||||||
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,
|
|
||||||
path: Cow<'a, ChannelPath>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(ChannelId, ChannelPath)> for ChannelLocation<'static> {
|
|
||||||
fn from(value: (ChannelId, ChannelPath)) -> Self {
|
|
||||||
ChannelLocation {
|
|
||||||
channel: value.0,
|
|
||||||
path: Cow::Owned(value.1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<(ChannelId, &'a ChannelPath)> for ChannelLocation<'a> {
|
|
||||||
fn from(value: (ChannelId, &'a ChannelPath)) -> Self {
|
|
||||||
ChannelLocation {
|
|
||||||
channel: value.0,
|
|
||||||
path: Cow::Borrowed(value.1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
settings::register::<panel_settings::CollaborationPanelSettings>(cx);
|
settings::register::<panel_settings::CollaborationPanelSettings>(cx);
|
||||||
contact_finder::init(cx);
|
contact_finder::init(cx);
|
||||||
|
@ -190,7 +166,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(CollabPanel::manage_members);
|
cx.add_action(CollabPanel::manage_members);
|
||||||
cx.add_action(CollabPanel::rename_selected_channel);
|
cx.add_action(CollabPanel::rename_selected_channel);
|
||||||
cx.add_action(CollabPanel::rename_channel);
|
cx.add_action(CollabPanel::rename_channel);
|
||||||
cx.add_action(CollabPanel::toggle_channel_collapsed);
|
cx.add_action(CollabPanel::toggle_channel_collapsed_action);
|
||||||
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);
|
||||||
|
@ -248,11 +224,11 @@ pub fn init(cx: &mut AppContext) {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ChannelEditingState {
|
pub enum ChannelEditingState {
|
||||||
Create {
|
Create {
|
||||||
location: Option<ChannelLocation<'static>>,
|
location: Option<ChannelPath>,
|
||||||
pending_name: Option<String>,
|
pending_name: Option<String>,
|
||||||
},
|
},
|
||||||
Rename {
|
Rename {
|
||||||
location: ChannelLocation<'static>,
|
location: ChannelPath,
|
||||||
pending_name: Option<String>,
|
pending_name: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -286,7 +262,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<ChannelLocation<'static>>,
|
collapsed_channels: Vec<ChannelPath>,
|
||||||
workspace: WeakViewHandle<Workspace>,
|
workspace: WeakViewHandle<Workspace>,
|
||||||
context_menu_on_selected: bool,
|
context_menu_on_selected: bool,
|
||||||
}
|
}
|
||||||
|
@ -294,7 +270,7 @@ pub struct CollabPanel {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct SerializedCollabPanel {
|
struct SerializedCollabPanel {
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
collapsed_channels: Option<Vec<ChannelLocation<'static>>>,
|
collapsed_channels: Option<Vec<ChannelPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -826,15 +802,13 @@ impl CollabPanel {
|
||||||
let (channel, path) = channel_store.channel_at_index(mat.candidate_id).unwrap();
|
let (channel, path) = channel_store.channel_at_index(mat.candidate_id).unwrap();
|
||||||
let depth = path.len() - 1;
|
let depth = path.len() - 1;
|
||||||
|
|
||||||
let location: ChannelLocation<'_> = (channel.id, path).into();
|
if collapse_depth.is_none() && self.is_channel_collapsed(path) {
|
||||||
|
|
||||||
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(&location) {
|
if self.is_channel_collapsed(path) {
|
||||||
collapse_depth = Some(depth);
|
collapse_depth = Some(depth);
|
||||||
} else {
|
} else {
|
||||||
collapse_depth = None;
|
collapse_depth = None;
|
||||||
|
@ -843,9 +817,9 @@ impl CollabPanel {
|
||||||
|
|
||||||
match &self.channel_editing_state {
|
match &self.channel_editing_state {
|
||||||
Some(ChannelEditingState::Create {
|
Some(ChannelEditingState::Create {
|
||||||
location: parent_id,
|
location: parent_path,
|
||||||
..
|
..
|
||||||
}) if *parent_id == Some(location) => {
|
}) if parent_path.as_ref() == Some(path) => {
|
||||||
self.entries.push(ListEntry::Channel {
|
self.entries.push(ListEntry::Channel {
|
||||||
channel: channel.clone(),
|
channel: channel.clone(),
|
||||||
depth,
|
depth,
|
||||||
|
@ -854,10 +828,10 @@ impl CollabPanel {
|
||||||
self.entries
|
self.entries
|
||||||
.push(ListEntry::ChannelEditor { depth: depth + 1 });
|
.push(ListEntry::ChannelEditor { depth: depth + 1 });
|
||||||
}
|
}
|
||||||
Some(ChannelEditingState::Rename { location, .. })
|
Some(ChannelEditingState::Rename {
|
||||||
if location.channel == channel.id
|
location: parent_path,
|
||||||
&& location.path == Cow::Borrowed(path) =>
|
..
|
||||||
{
|
}) if parent_path == path => {
|
||||||
self.entries.push(ListEntry::ChannelEditor { depth });
|
self.entries.push(ListEntry::ChannelEditor { depth });
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -1674,13 +1648,7 @@ impl CollabPanel {
|
||||||
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 = {
|
let disclosed = has_children.then(|| !self.collapsed_channels.binary_search(&path).is_ok());
|
||||||
let location = ChannelLocation {
|
|
||||||
channel: channel_id,
|
|
||||||
path: 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)
|
||||||
|
@ -1696,7 +1664,7 @@ impl CollabPanel {
|
||||||
|
|
||||||
enum ChannelCall {}
|
enum ChannelCall {}
|
||||||
|
|
||||||
MouseEventHandler::new::<Channel, _>(id(&path) as usize, cx, |state, cx| {
|
MouseEventHandler::new::<Channel, _>(path.unique_id() as usize, cx, |state, cx| {
|
||||||
let row_hovered = state.hovered();
|
let row_hovered = state.hovered();
|
||||||
|
|
||||||
Flex::<Self>::row()
|
Flex::<Self>::row()
|
||||||
|
@ -1767,10 +1735,10 @@ impl CollabPanel {
|
||||||
.disclosable(
|
.disclosable(
|
||||||
disclosed,
|
disclosed,
|
||||||
Box::new(ToggleCollapse {
|
Box::new(ToggleCollapse {
|
||||||
location: (channel_id, path.clone()).into(),
|
location: path.clone(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.with_id(id(&path) as usize)
|
.with_id(path.unique_id() as usize)
|
||||||
.with_style(theme.disclosure.clone())
|
.with_style(theme.disclosure.clone())
|
||||||
.element()
|
.element()
|
||||||
.constrained()
|
.constrained()
|
||||||
|
@ -1786,11 +1754,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(
|
this.deploy_channel_context_menu(Some(e.position), &path, cx);
|
||||||
Some(e.position),
|
|
||||||
&(channel_id, path.clone()).into(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.into_any()
|
.into_any()
|
||||||
|
@ -2037,7 +2001,7 @@ impl CollabPanel {
|
||||||
fn deploy_channel_context_menu(
|
fn deploy_channel_context_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
position: Option<Vector2F>,
|
position: Option<Vector2F>,
|
||||||
location: &ChannelLocation<'static>,
|
path: &ChannelPath,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.context_menu_on_selected = position.is_none();
|
self.context_menu_on_selected = position.is_none();
|
||||||
|
@ -2063,20 +2027,16 @@ impl CollabPanel {
|
||||||
if let Some(channel_name) = channel_name {
|
if let Some(channel_name) = channel_name {
|
||||||
items.push(ContextMenuItem::action(
|
items.push(ContextMenuItem::action(
|
||||||
format!("Move '#{}' here", channel_name),
|
format!("Move '#{}' here", channel_name),
|
||||||
MoveChannel {
|
MoveChannel { to: path.channel_id() },
|
||||||
to: location.channel,
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
items.push(ContextMenuItem::action(
|
items.push(ContextMenuItem::action(
|
||||||
format!("Link '#{}' here", channel_name),
|
format!("Link '#{}' here", channel_name),
|
||||||
LinkChannel {
|
LinkChannel { to: path.channel_id() },
|
||||||
to: location.channel,
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
items.push(ContextMenuItem::Separator)
|
items.push(ContextMenuItem::Separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
let expand_action_name = if self.is_channel_collapsed(&location) {
|
let expand_action_name = if self.is_channel_collapsed(&path) {
|
||||||
"Expand Subchannels"
|
"Expand Subchannels"
|
||||||
} else {
|
} else {
|
||||||
"Collapse Subchannels"
|
"Collapse Subchannels"
|
||||||
|
@ -2086,32 +2046,27 @@ impl CollabPanel {
|
||||||
ContextMenuItem::action(
|
ContextMenuItem::action(
|
||||||
expand_action_name,
|
expand_action_name,
|
||||||
ToggleCollapse {
|
ToggleCollapse {
|
||||||
location: location.clone(),
|
location: path.clone(),
|
||||||
},
|
|
||||||
),
|
|
||||||
ContextMenuItem::action(
|
|
||||||
"Open Notes",
|
|
||||||
OpenChannelBuffer {
|
|
||||||
channel_id: location.channel,
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ContextMenuItem::action("Open Notes", OpenChannelBuffer { channel_id: path.channel_id() }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if self.channel_store.read(cx).is_user_admin(location.channel) {
|
if self.channel_store.read(cx).is_user_admin(path.channel_id()) {
|
||||||
let parent_id = location.path.parent_id();
|
let parent_id = path.parent_id();
|
||||||
|
|
||||||
items.extend([
|
items.extend([
|
||||||
ContextMenuItem::Separator,
|
ContextMenuItem::Separator,
|
||||||
ContextMenuItem::action(
|
ContextMenuItem::action(
|
||||||
"New Subchannel",
|
"New Subchannel",
|
||||||
NewChannel {
|
NewChannel {
|
||||||
location: location.clone(),
|
location: path.clone(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ContextMenuItem::action(
|
ContextMenuItem::action(
|
||||||
"Rename",
|
"Rename",
|
||||||
RenameChannel {
|
RenameChannel {
|
||||||
location: location.clone(),
|
location: path.clone(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ContextMenuItem::Separator,
|
ContextMenuItem::Separator,
|
||||||
|
@ -2121,7 +2076,7 @@ impl CollabPanel {
|
||||||
items.push(ContextMenuItem::action(
|
items.push(ContextMenuItem::action(
|
||||||
"Unlink from parent",
|
"Unlink from parent",
|
||||||
UnlinkChannel {
|
UnlinkChannel {
|
||||||
channel_id: location.channel,
|
channel_id: path.channel_id(),
|
||||||
parent_id,
|
parent_id,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
@ -2130,7 +2085,7 @@ impl CollabPanel {
|
||||||
items.extend([ContextMenuItem::action(
|
items.extend([ContextMenuItem::action(
|
||||||
"Move this channel",
|
"Move this channel",
|
||||||
StartMoveChannel {
|
StartMoveChannel {
|
||||||
channel_id: location.channel,
|
channel_id: path.channel_id(),
|
||||||
parent_id,
|
parent_id,
|
||||||
},
|
},
|
||||||
)]);
|
)]);
|
||||||
|
@ -2140,20 +2095,20 @@ impl CollabPanel {
|
||||||
ContextMenuItem::action(
|
ContextMenuItem::action(
|
||||||
"Invite Members",
|
"Invite Members",
|
||||||
InviteMembers {
|
InviteMembers {
|
||||||
channel_id: location.channel,
|
channel_id: path.channel_id(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ContextMenuItem::action(
|
ContextMenuItem::action(
|
||||||
"Manage Members",
|
"Manage Members",
|
||||||
ManageMembers {
|
ManageMembers {
|
||||||
channel_id: location.channel,
|
channel_id: path.channel_id(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ContextMenuItem::Separator,
|
ContextMenuItem::Separator,
|
||||||
ContextMenuItem::action(
|
ContextMenuItem::action(
|
||||||
"Delete",
|
"Delete",
|
||||||
RemoveChannel {
|
RemoveChannel {
|
||||||
channel_id: location.channel,
|
channel_id: path.channel_id(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
@ -2296,7 +2251,7 @@ impl CollabPanel {
|
||||||
.update(cx, |channel_store, cx| {
|
.update(cx, |channel_store, cx| {
|
||||||
channel_store.create_channel(
|
channel_store.create_channel(
|
||||||
&channel_name,
|
&channel_name,
|
||||||
location.as_ref().map(|location| location.channel),
|
location.as_ref().map(|location| location.channel_id()),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -2315,7 +2270,7 @@ impl CollabPanel {
|
||||||
|
|
||||||
self.channel_store
|
self.channel_store
|
||||||
.update(cx, |channel_store, cx| {
|
.update(cx, |channel_store, cx| {
|
||||||
channel_store.rename(location.channel, &channel_name, cx)
|
channel_store.rename(location.channel_id(), &channel_name, cx)
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -2342,58 +2297,48 @@ impl CollabPanel {
|
||||||
_: &CollapseSelectedChannel,
|
_: &CollapseSelectedChannel,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let Some((channel_id, path)) = self
|
let Some((_, path)) = self
|
||||||
.selected_channel()
|
.selected_channel()
|
||||||
.map(|(channel, parent)| (channel.id, parent))
|
.map(|(channel, parent)| (channel.id, parent))
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = path.to_owned();
|
if self.is_channel_collapsed(&path) {
|
||||||
|
|
||||||
if self.is_channel_collapsed(&(channel_id, path.clone()).into()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.toggle_channel_collapsed(
|
self.toggle_channel_collapsed(&path.clone(), cx);
|
||||||
&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, path)) = self
|
let Some((_, path)) = self
|
||||||
.selected_channel()
|
.selected_channel()
|
||||||
.map(|(channel, parent)| (channel.id, parent))
|
.map(|(channel, parent)| (channel.id, parent))
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = path.to_owned();
|
if !self.is_channel_collapsed(&path) {
|
||||||
|
|
||||||
if !self.is_channel_collapsed(&(channel_id, path.clone()).into()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.toggle_channel_collapsed(
|
self.toggle_channel_collapsed(path.to_owned(), cx)
|
||||||
&ToggleCollapse {
|
|
||||||
location: (channel_id, path).into(),
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_channel_collapsed(&mut self, action: &ToggleCollapse, cx: &mut ViewContext<Self>) {
|
fn toggle_channel_collapsed_action(&mut self, action: &ToggleCollapse, cx: &mut ViewContext<Self>) {
|
||||||
let location = action.location.clone();
|
self.toggle_channel_collapsed(&action.location, cx);
|
||||||
|
}
|
||||||
|
|
||||||
match self.collapsed_channels.binary_search(&location) {
|
fn toggle_channel_collapsed<'a>(&mut self, path: impl Into<Cow<'a, ChannelPath>>, cx: &mut ViewContext<Self>) {
|
||||||
|
let path = path.into();
|
||||||
|
match self.collapsed_channels.binary_search(&path) {
|
||||||
Ok(ix) => {
|
Ok(ix) => {
|
||||||
self.collapsed_channels.remove(ix);
|
self.collapsed_channels.remove(ix);
|
||||||
}
|
}
|
||||||
Err(ix) => {
|
Err(ix) => {
|
||||||
self.collapsed_channels.insert(ix, location);
|
self.collapsed_channels.insert(ix, path.into_owned());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.serialize(cx);
|
self.serialize(cx);
|
||||||
|
@ -2402,8 +2347,8 @@ impl CollabPanel {
|
||||||
cx.focus_self();
|
cx.focus_self();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_channel_collapsed(&self, location: &ChannelLocation) -> bool {
|
fn is_channel_collapsed(&self, path: &ChannelPath) -> bool {
|
||||||
self.collapsed_channels.binary_search(location).is_ok()
|
self.collapsed_channels.binary_search(path).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leave_call(cx: &mut ViewContext<Self>) {
|
fn leave_call(cx: &mut ViewContext<Self>) {
|
||||||
|
@ -2472,10 +2417,10 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
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, parent)) = self.selected_channel() {
|
if let Some((_, parent)) = self.selected_channel() {
|
||||||
self.rename_channel(
|
self.rename_channel(
|
||||||
&RenameChannel {
|
&RenameChannel {
|
||||||
location: (channel.id, parent.to_owned()).into(),
|
location: parent.to_owned(),
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -2484,11 +2429,11 @@ 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.location.channel) {
|
if !channel_store.is_user_admin(action.location.channel_id()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(channel) = channel_store
|
if let Some(channel) = channel_store
|
||||||
.channel_for_id(action.location.channel)
|
.channel_for_id(action.location.channel_id())
|
||||||
.cloned()
|
.cloned()
|
||||||
{
|
{
|
||||||
self.channel_editing_state = Some(ChannelEditingState::Rename {
|
self.channel_editing_state = Some(ChannelEditingState::Rename {
|
||||||
|
@ -2512,11 +2457,11 @@ 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, path)) = self.selected_channel() else {
|
let Some((_, path)) = self.selected_channel() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.deploy_channel_context_menu(None, &(channel.id, path.to_owned()).into(), cx);
|
self.deploy_channel_context_menu(None, &path.to_owned(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_channel(&self) -> Option<(&Arc<Channel>, &ChannelPath)> {
|
fn selected_channel(&self) -> Option<(&Arc<Channel>, &ChannelPath)> {
|
||||||
|
@ -2983,26 +2928,3 @@ 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 Fowler–Noll–Vo 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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue