Add hover styles to channels matching the current selection
Fix chat desync from moving / linking channels
This commit is contained in:
parent
d5f0ce0e20
commit
ac65e7590c
8 changed files with 284 additions and 94 deletions
|
@ -585,6 +585,14 @@
|
||||||
"space": "menu::Confirm"
|
"space": "menu::Confirm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "CollabPanel > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"cmd-c": "collab_panel::StartLinkChannel",
|
||||||
|
"cmd-x": "collab_panel::StartMoveChannel",
|
||||||
|
"cmd-v": "collab_panel::MoveOrLinkToSelected"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ChannelModal",
|
"context": "ChannelModal",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|
|
@ -146,17 +146,26 @@ impl ChannelStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of unique channels in the store
|
||||||
pub fn channel_count(&self) -> usize {
|
pub fn channel_count(&self) -> usize {
|
||||||
self.channel_index.len()
|
self.channel_index.by_id().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the index of a channel ID in the list of unique channels
|
||||||
pub fn index_of_channel(&self, channel_id: ChannelId) -> Option<usize> {
|
pub fn index_of_channel(&self, channel_id: ChannelId) -> Option<usize> {
|
||||||
self.channel_index
|
self.channel_index
|
||||||
.iter()
|
.by_id()
|
||||||
.position(|path| path.ends_with(&[channel_id]))
|
.keys()
|
||||||
|
.position(|id| *id == channel_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channels(&self) -> impl '_ + Iterator<Item = (usize, &Arc<Channel>)> {
|
/// Returns an iterator over all unique channels
|
||||||
|
pub fn channels(&self) -> impl '_ + Iterator<Item = &Arc<Channel>> {
|
||||||
|
self.channel_index.by_id().values()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all entries in the channel DAG
|
||||||
|
pub fn channel_dag_entries(&self) -> impl '_ + Iterator<Item = (usize, &Arc<Channel>)> {
|
||||||
self.channel_index.iter().map(move |path| {
|
self.channel_index.iter().map(move |path| {
|
||||||
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();
|
||||||
|
@ -164,7 +173,7 @@ impl ChannelStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_at_index(&self, ix: usize) -> Option<(&Arc<Channel>, &ChannelPath)> {
|
pub fn channel_dag_entry_at(&self, ix: usize) -> Option<(&Arc<Channel>, &ChannelPath)> {
|
||||||
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();
|
||||||
|
@ -172,6 +181,10 @@ impl ChannelStore {
|
||||||
Some((channel, path))
|
Some((channel, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn channel_at(&self, ix: usize) -> Option<&Arc<Channel>> {
|
||||||
|
self.channel_index.by_id().values().nth(ix)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn channel_invitations(&self) -> &[Arc<Channel>] {
|
pub fn channel_invitations(&self) -> &[Arc<Channel>] {
|
||||||
&self.channel_invitations
|
&self.channel_invitations
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{ops::Deref, sync::Arc};
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use crate::{Channel, ChannelId};
|
use crate::{Channel, ChannelId};
|
||||||
use collections::HashMap;
|
use collections::BTreeMap;
|
||||||
use rpc::proto;
|
use rpc::proto;
|
||||||
|
|
||||||
use super::ChannelPath;
|
use super::ChannelPath;
|
||||||
|
@ -9,11 +9,11 @@ use super::ChannelPath;
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ChannelIndex {
|
pub struct ChannelIndex {
|
||||||
paths: Vec<ChannelPath>,
|
paths: Vec<ChannelPath>,
|
||||||
channels_by_id: HashMap<ChannelId, Arc<Channel>>,
|
channels_by_id: BTreeMap<ChannelId, Arc<Channel>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelIndex {
|
impl ChannelIndex {
|
||||||
pub fn by_id(&self) -> &HashMap<ChannelId, Arc<Channel>> {
|
pub fn by_id(&self) -> &BTreeMap<ChannelId, Arc<Channel>> {
|
||||||
&self.channels_by_id
|
&self.channels_by_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ impl Deref for ChannelIndex {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ChannelPathsInsertGuard<'a> {
|
pub struct ChannelPathsInsertGuard<'a> {
|
||||||
paths: &'a mut Vec<ChannelPath>,
|
paths: &'a mut Vec<ChannelPath>,
|
||||||
channels_by_id: &'a mut HashMap<ChannelId, Arc<Channel>>,
|
channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ChannelPathsInsertGuard<'a> {
|
impl<'a> ChannelPathsInsertGuard<'a> {
|
||||||
|
@ -155,7 +155,7 @@ impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
||||||
|
|
||||||
fn channel_path_sorting_key<'a>(
|
fn channel_path_sorting_key<'a>(
|
||||||
path: &'a [ChannelId],
|
path: &'a [ChannelId],
|
||||||
channels_by_id: &'a HashMap<ChannelId, Arc<Channel>>,
|
channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
|
||||||
) -> impl 'a + Iterator<Item = Option<&'a str>> {
|
) -> impl 'a + Iterator<Item = Option<&'a str>> {
|
||||||
path.iter()
|
path.iter()
|
||||||
.map(|id| Some(channels_by_id.get(id)?.name.as_str()))
|
.map(|id| Some(channels_by_id.get(id)?.name.as_str()))
|
||||||
|
|
|
@ -181,7 +181,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
// Join a channel and populate its existing messages.
|
// Join a channel and populate its existing messages.
|
||||||
let channel = channel_store.update(cx, |store, cx| {
|
let channel = channel_store.update(cx, |store, cx| {
|
||||||
let channel_id = store.channels().next().unwrap().1.id;
|
let channel_id = store.channel_dag_entries().next().unwrap().1.id;
|
||||||
store.open_channel_chat(channel_id, cx)
|
store.open_channel_chat(channel_id, cx)
|
||||||
});
|
});
|
||||||
let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
|
let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
|
||||||
|
@ -363,7 +363,7 @@ fn assert_channels(
|
||||||
) {
|
) {
|
||||||
let actual = channel_store.read_with(cx, |store, _| {
|
let actual = channel_store.read_with(cx, |store, _| {
|
||||||
store
|
store
|
||||||
.channels()
|
.channel_dag_entries()
|
||||||
.map(|(depth, channel)| {
|
.map(|(depth, channel)| {
|
||||||
(
|
(
|
||||||
depth,
|
depth,
|
||||||
|
|
|
@ -56,7 +56,10 @@ async fn test_core_channels(
|
||||||
);
|
);
|
||||||
|
|
||||||
client_b.channel_store().read_with(cx_b, |channels, _| {
|
client_b.channel_store().read_with(cx_b, |channels, _| {
|
||||||
assert!(channels.channels().collect::<Vec<_>>().is_empty())
|
assert!(channels
|
||||||
|
.channel_dag_entries()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.is_empty())
|
||||||
});
|
});
|
||||||
|
|
||||||
// Invite client B to channel A as client A.
|
// Invite client B to channel A as client A.
|
||||||
|
@ -1170,7 +1173,7 @@ fn assert_channels(
|
||||||
) {
|
) {
|
||||||
let actual = channel_store.read_with(cx, |store, _| {
|
let actual = channel_store.read_with(cx, |store, _| {
|
||||||
store
|
store
|
||||||
.channels()
|
.channel_dag_entries()
|
||||||
.map(|(depth, channel)| ExpectedChannel {
|
.map(|(depth, channel)| ExpectedChannel {
|
||||||
depth,
|
depth,
|
||||||
name: channel.name.clone(),
|
name: channel.name.clone(),
|
||||||
|
@ -1192,7 +1195,7 @@ fn assert_channels_list_shape(
|
||||||
|
|
||||||
let actual = channel_store.read_with(cx, |store, _| {
|
let actual = channel_store.read_with(cx, |store, _| {
|
||||||
store
|
store
|
||||||
.channels()
|
.channel_dag_entries()
|
||||||
.map(|(depth, channel)| (channel.id, depth))
|
.map(|(depth, channel)| (channel.id, depth))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
});
|
});
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl RandomizedTest for RandomChannelBufferTest {
|
||||||
match rng.gen_range(0..100_u32) {
|
match rng.gen_range(0..100_u32) {
|
||||||
0..=29 => {
|
0..=29 => {
|
||||||
let channel_name = client.channel_store().read_with(cx, |store, cx| {
|
let channel_name = client.channel_store().read_with(cx, |store, cx| {
|
||||||
store.channels().find_map(|(_, channel)| {
|
store.channel_dag_entries().find_map(|(_, channel)| {
|
||||||
if store.has_open_channel_buffer(channel.id, cx) {
|
if store.has_open_channel_buffer(channel.id, cx) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -133,7 +133,7 @@ impl RandomizedTest for RandomChannelBufferTest {
|
||||||
ChannelBufferOperation::JoinChannelNotes { channel_name } => {
|
ChannelBufferOperation::JoinChannelNotes { channel_name } => {
|
||||||
let buffer = client.channel_store().update(cx, |store, cx| {
|
let buffer = client.channel_store().update(cx, |store, cx| {
|
||||||
let channel_id = store
|
let channel_id = store
|
||||||
.channels()
|
.channel_dag_entries()
|
||||||
.find(|(_, c)| c.name == channel_name)
|
.find(|(_, c)| c.name == channel_name)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.1
|
.1
|
||||||
|
|
|
@ -166,8 +166,8 @@ impl ChatPanel {
|
||||||
let selected_channel_id = this
|
let selected_channel_id = this
|
||||||
.channel_store
|
.channel_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.channel_at_index(selected_ix)
|
.channel_at(selected_ix)
|
||||||
.map(|e| e.0.id);
|
.map(|e| e.id);
|
||||||
if let Some(selected_channel_id) = selected_channel_id {
|
if let Some(selected_channel_id) = selected_channel_id {
|
||||||
this.select_channel(selected_channel_id, cx)
|
this.select_channel(selected_channel_id, cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
@ -391,7 +391,7 @@ impl ChatPanel {
|
||||||
(ItemType::Unselected, true) => &theme.channel_select.hovered_item,
|
(ItemType::Unselected, true) => &theme.channel_select.hovered_item,
|
||||||
};
|
};
|
||||||
|
|
||||||
let channel = &channel_store.read(cx).channel_at_index(ix).unwrap().0;
|
let channel = &channel_store.read(cx).channel_at(ix).unwrap();
|
||||||
let channel_id = channel.id;
|
let channel_id = channel.id;
|
||||||
|
|
||||||
let mut row = Flex::row()
|
let mut row = Flex::row()
|
||||||
|
|
|
@ -23,9 +23,9 @@ use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
elements::{
|
elements::{
|
||||||
Canvas, ChildView, Component, Empty, Flex, Image, Label, List, ListOffset, ListState,
|
Canvas, ChildView, Component, ContainerStyle, Empty, Flex, Image, Label, List, ListOffset,
|
||||||
MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement, SafeStylable,
|
ListState, MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement,
|
||||||
Stack, Svg,
|
SafeStylable, Stack, Svg,
|
||||||
},
|
},
|
||||||
fonts::TextStyle,
|
fonts::TextStyle,
|
||||||
geometry::{
|
geometry::{
|
||||||
|
@ -42,7 +42,7 @@ use project::{Fs, Project};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::{borrow::Cow, hash::Hash, mem, sync::Arc};
|
use std::{borrow::Cow, hash::Hash, mem, sync::Arc};
|
||||||
use theme::{components::ComponentExt, IconButton};
|
use theme::{components::ComponentExt, IconButton, Interactive};
|
||||||
use util::{iife, ResultExt, TryFutureExt};
|
use util::{iife, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel},
|
dock::{DockPosition, Panel},
|
||||||
|
@ -65,6 +65,11 @@ struct RenameChannel {
|
||||||
location: ChannelPath,
|
location: ChannelPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
struct ToggleSelectedIx {
|
||||||
|
ix: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
struct RemoveChannel {
|
struct RemoveChannel {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
|
@ -96,7 +101,13 @@ struct OpenChannelBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
struct StartMoveChannel {
|
struct StartMoveChannelFor {
|
||||||
|
channel_id: ChannelId,
|
||||||
|
parent_id: Option<ChannelId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
struct StartLinkChannelFor {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
parent_id: Option<ChannelId>,
|
parent_id: Option<ChannelId>,
|
||||||
}
|
}
|
||||||
|
@ -126,7 +137,10 @@ actions!(
|
||||||
Remove,
|
Remove,
|
||||||
Secondary,
|
Secondary,
|
||||||
CollapseSelectedChannel,
|
CollapseSelectedChannel,
|
||||||
ExpandSelectedChannel
|
ExpandSelectedChannel,
|
||||||
|
StartMoveChannel,
|
||||||
|
StartLinkChannel,
|
||||||
|
MoveOrLinkToSelected,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -143,12 +157,27 @@ impl_actions!(
|
||||||
JoinChannelCall,
|
JoinChannelCall,
|
||||||
OpenChannelBuffer,
|
OpenChannelBuffer,
|
||||||
LinkChannel,
|
LinkChannel,
|
||||||
StartMoveChannel,
|
StartMoveChannelFor,
|
||||||
|
StartLinkChannelFor,
|
||||||
MoveChannel,
|
MoveChannel,
|
||||||
UnlinkChannel
|
UnlinkChannel,
|
||||||
|
ToggleSelectedIx
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
struct ChannelMoveClipboard {
|
||||||
|
channel_id: ChannelId,
|
||||||
|
parent_id: Option<ChannelId>,
|
||||||
|
intent: ClipboardIntent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum ClipboardIntent {
|
||||||
|
Move,
|
||||||
|
Link,
|
||||||
|
}
|
||||||
|
|
||||||
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
|
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
@ -175,17 +204,99 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(CollabPanel::open_channel_notes);
|
cx.add_action(CollabPanel::open_channel_notes);
|
||||||
|
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|panel: &mut CollabPanel, action: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
|
|panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
|
||||||
panel.channel_move = Some(*action);
|
if panel.selection.take() != Some(action.ix) {
|
||||||
|
panel.selection = Some(action.ix)
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.add_action(
|
||||||
|
|panel: &mut CollabPanel,
|
||||||
|
action: &StartMoveChannelFor,
|
||||||
|
_: &mut ViewContext<CollabPanel>| {
|
||||||
|
panel.channel_clipboard = Some(ChannelMoveClipboard {
|
||||||
|
channel_id: action.channel_id,
|
||||||
|
parent_id: action.parent_id,
|
||||||
|
intent: ClipboardIntent::Move,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.add_action(
|
||||||
|
|panel: &mut CollabPanel,
|
||||||
|
action: &StartLinkChannelFor,
|
||||||
|
_: &mut ViewContext<CollabPanel>| {
|
||||||
|
panel.channel_clipboard = Some(ChannelMoveClipboard {
|
||||||
|
channel_id: action.channel_id,
|
||||||
|
parent_id: action.parent_id,
|
||||||
|
intent: ClipboardIntent::Link,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.add_action(
|
||||||
|
|panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
|
||||||
|
if let Some((_, path)) = panel.selected_channel() {
|
||||||
|
panel.channel_clipboard = Some(ChannelMoveClipboard {
|
||||||
|
channel_id: path.channel_id(),
|
||||||
|
parent_id: path.parent_id(),
|
||||||
|
intent: ClipboardIntent::Move,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.add_action(
|
||||||
|
|panel: &mut CollabPanel, _: &StartLinkChannel, _: &mut ViewContext<CollabPanel>| {
|
||||||
|
if let Some((_, path)) = panel.selected_channel() {
|
||||||
|
panel.channel_clipboard = Some(ChannelMoveClipboard {
|
||||||
|
channel_id: path.channel_id(),
|
||||||
|
parent_id: path.parent_id(),
|
||||||
|
intent: ClipboardIntent::Link,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.add_action(
|
||||||
|
|panel: &mut CollabPanel, _: &MoveOrLinkToSelected, cx: &mut ViewContext<CollabPanel>| {
|
||||||
|
let clipboard = panel.channel_clipboard.take();
|
||||||
|
if let Some(((selected_channel, _), clipboard)) =
|
||||||
|
panel.selected_channel().zip(clipboard)
|
||||||
|
{
|
||||||
|
match clipboard.intent {
|
||||||
|
ClipboardIntent::Move if clipboard.parent_id.is_some() => {
|
||||||
|
let parent_id = clipboard.parent_id.unwrap();
|
||||||
|
panel.channel_store.update(cx, |channel_store, cx| {
|
||||||
|
channel_store
|
||||||
|
.move_channel(
|
||||||
|
clipboard.channel_id,
|
||||||
|
parent_id,
|
||||||
|
selected_channel.id,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.detach_and_log_err(cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => panel.channel_store.update(cx, |channel_store, cx| {
|
||||||
|
channel_store
|
||||||
|
.link_channel(clipboard.channel_id, selected_channel.id, cx)
|
||||||
|
.detach_and_log_err(cx)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|panel: &mut CollabPanel, action: &LinkChannel, cx: &mut ViewContext<CollabPanel>| {
|
|panel: &mut CollabPanel, action: &LinkChannel, cx: &mut ViewContext<CollabPanel>| {
|
||||||
if let Some(move_start) = panel.channel_move.take() {
|
if let Some(clipboard) = panel.channel_clipboard.take() {
|
||||||
panel.channel_store.update(cx, |channel_store, cx| {
|
panel.channel_store.update(cx, |channel_store, cx| {
|
||||||
channel_store
|
channel_store
|
||||||
.link_channel(move_start.channel_id, action.to, cx)
|
.link_channel(clipboard.channel_id, action.to, cx)
|
||||||
.detach_and_log_err(cx)
|
.detach_and_log_err(cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -194,15 +305,15 @@ pub fn init(cx: &mut AppContext) {
|
||||||
|
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
|
|panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
|
||||||
if let Some(move_start) = panel.channel_move.take() {
|
if let Some(clipboard) = panel.channel_clipboard.take() {
|
||||||
panel.channel_store.update(cx, |channel_store, cx| {
|
panel.channel_store.update(cx, |channel_store, cx| {
|
||||||
if let Some(parent) = move_start.parent_id {
|
if let Some(parent) = clipboard.parent_id {
|
||||||
channel_store
|
channel_store
|
||||||
.move_channel(move_start.channel_id, parent, action.to, cx)
|
.move_channel(clipboard.channel_id, parent, action.to, cx)
|
||||||
.detach_and_log_err(cx)
|
.detach_and_log_err(cx)
|
||||||
} else {
|
} else {
|
||||||
channel_store
|
channel_store
|
||||||
.link_channel(move_start.channel_id, action.to, cx)
|
.link_channel(clipboard.channel_id, action.to, cx)
|
||||||
.detach_and_log_err(cx)
|
.detach_and_log_err(cx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -246,7 +357,7 @@ pub struct CollabPanel {
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
has_focus: bool,
|
has_focus: bool,
|
||||||
channel_move: Option<StartMoveChannel>,
|
channel_clipboard: Option<ChannelMoveClipboard>,
|
||||||
pending_serialization: Task<Option<()>>,
|
pending_serialization: Task<Option<()>>,
|
||||||
context_menu: ViewHandle<ContextMenu>,
|
context_menu: ViewHandle<ContextMenu>,
|
||||||
filter_editor: ViewHandle<Editor>,
|
filter_editor: ViewHandle<Editor>,
|
||||||
|
@ -444,6 +555,7 @@ impl CollabPanel {
|
||||||
path.to_owned(),
|
path.to_owned(),
|
||||||
&theme.collab_panel,
|
&theme.collab_panel,
|
||||||
is_selected,
|
is_selected,
|
||||||
|
ix,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -510,7 +622,7 @@ impl CollabPanel {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
width: None,
|
width: None,
|
||||||
has_focus: false,
|
has_focus: false,
|
||||||
channel_move: None,
|
channel_clipboard: 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)),
|
||||||
|
@ -776,16 +888,13 @@ impl CollabPanel {
|
||||||
if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
|
if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
|
||||||
self.match_candidates.clear();
|
self.match_candidates.clear();
|
||||||
self.match_candidates
|
self.match_candidates
|
||||||
.extend(
|
.extend(channel_store.channel_dag_entries().enumerate().map(
|
||||||
channel_store
|
|(ix, (_, channel))| StringMatchCandidate {
|
||||||
.channels()
|
id: ix,
|
||||||
.enumerate()
|
string: channel.name.clone(),
|
||||||
.map(|(ix, (_, channel))| StringMatchCandidate {
|
char_bag: channel.name.chars().collect(),
|
||||||
id: ix,
|
},
|
||||||
string: channel.name.clone(),
|
));
|
||||||
char_bag: channel.name.chars().collect(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let matches = executor.block(match_strings(
|
let matches = executor.block(match_strings(
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
|
@ -801,7 +910,9 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
let mut collapse_depth = None;
|
let mut collapse_depth = None;
|
||||||
for mat in matches {
|
for mat in matches {
|
||||||
let (channel, path) = channel_store.channel_at_index(mat.candidate_id).unwrap();
|
let (channel, path) = channel_store
|
||||||
|
.channel_dag_entry_at(mat.candidate_id)
|
||||||
|
.unwrap();
|
||||||
let depth = path.len() - 1;
|
let depth = path.len() - 1;
|
||||||
|
|
||||||
if collapse_depth.is_none() && self.is_channel_collapsed(path) {
|
if collapse_depth.is_none() && self.is_channel_collapsed(path) {
|
||||||
|
@ -1627,7 +1738,7 @@ impl CollabPanel {
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(theme.collab_panel.row_height)
|
.with_height(theme.collab_panel.row_height)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(gpui::elements::ContainerStyle {
|
.with_style(ContainerStyle {
|
||||||
background_color: Some(theme.editor.background),
|
background_color: Some(theme.editor.background),
|
||||||
..*theme.collab_panel.contact_row.default_style()
|
..*theme.collab_panel.contact_row.default_style()
|
||||||
})
|
})
|
||||||
|
@ -1645,11 +1756,13 @@ impl CollabPanel {
|
||||||
path: ChannelPath,
|
path: ChannelPath,
|
||||||
theme: &theme::CollabPanel,
|
theme: &theme::CollabPanel,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
|
ix: usize,
|
||||||
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 other_selected =
|
||||||
|
self.selected_channel().map(|channel| channel.0.id) == Some(channel.id);
|
||||||
let disclosed = has_children.then(|| !self.collapsed_channels.binary_search(&path).is_ok());
|
let disclosed = has_children.then(|| !self.collapsed_channels.binary_search(&path).is_ok());
|
||||||
|
|
||||||
let is_active = iife!({
|
let is_active = iife!({
|
||||||
|
@ -1683,6 +1796,20 @@ impl CollabPanel {
|
||||||
MouseEventHandler::new::<Channel, _>(path.unique_id() 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();
|
||||||
|
|
||||||
|
let mut select_state = |interactive: &Interactive<ContainerStyle>| {
|
||||||
|
if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() {
|
||||||
|
interactive.clicked.as_ref().unwrap().clone()
|
||||||
|
} else if state.hovered() || other_selected {
|
||||||
|
interactive
|
||||||
|
.hovered
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&interactive.default)
|
||||||
|
.clone()
|
||||||
|
} else {
|
||||||
|
interactive.default.clone()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Flex::<Self>::row()
|
Flex::<Self>::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
Svg::new("icons/hash.svg")
|
Svg::new("icons/hash.svg")
|
||||||
|
@ -1760,11 +1887,11 @@ impl CollabPanel {
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(theme.row_height)
|
.with_height(theme.row_height)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(
|
.with_style(select_state(
|
||||||
*theme
|
theme
|
||||||
.channel_row
|
.channel_row
|
||||||
.style_for(is_selected || is_active || is_dragged_over, state),
|
.in_state(is_selected || is_active || is_dragged_over),
|
||||||
)
|
))
|
||||||
.with_padding_left(
|
.with_padding_left(
|
||||||
theme.channel_row.default_style().padding.left
|
theme.channel_row.default_style().padding.left
|
||||||
+ theme.channel_indent * depth as f32,
|
+ theme.channel_indent * depth as f32,
|
||||||
|
@ -1778,7 +1905,7 @@ impl CollabPanel {
|
||||||
.on_click(MouseButton::Right, {
|
.on_click(MouseButton::Right, {
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
move |e, this, cx| {
|
move |e, this, cx| {
|
||||||
this.deploy_channel_context_menu(Some(e.position), &path, cx);
|
this.deploy_channel_context_menu(Some(e.position), &path, ix, cx);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_up(MouseButton::Left, move |e, this, cx| {
|
.on_up(MouseButton::Left, move |e, this, cx| {
|
||||||
|
@ -2119,15 +2246,34 @@ impl CollabPanel {
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_subchannels(&self, ix: usize) -> bool {
|
||||||
|
self.entries
|
||||||
|
.get(ix)
|
||||||
|
.zip(self.entries.get(ix + 1))
|
||||||
|
.map(|entries| match entries {
|
||||||
|
(
|
||||||
|
ListEntry::Channel {
|
||||||
|
path: this_path, ..
|
||||||
|
},
|
||||||
|
ListEntry::Channel {
|
||||||
|
path: next_path, ..
|
||||||
|
},
|
||||||
|
) => next_path.starts_with(this_path),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
fn deploy_channel_context_menu(
|
fn deploy_channel_context_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
position: Option<Vector2F>,
|
position: Option<Vector2F>,
|
||||||
path: &ChannelPath,
|
path: &ChannelPath,
|
||||||
|
ix: usize,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.context_menu_on_selected = position.is_none();
|
self.context_menu_on_selected = position.is_none();
|
||||||
|
|
||||||
let channel_name = self.channel_move.as_ref().and_then(|channel| {
|
let channel_name = self.channel_clipboard.as_ref().and_then(|channel| {
|
||||||
let channel_name = self
|
let channel_name = self
|
||||||
.channel_store
|
.channel_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
@ -2145,42 +2291,37 @@ impl CollabPanel {
|
||||||
|
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
|
||||||
if let Some(channel_name) = channel_name {
|
let select_action_name = if self.selection == Some(ix) {
|
||||||
items.push(ContextMenuItem::action(
|
"Unselect"
|
||||||
format!("Move '#{}' here", channel_name),
|
|
||||||
MoveChannel {
|
|
||||||
to: path.channel_id(),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
items.push(ContextMenuItem::action(
|
|
||||||
format!("Link '#{}' here", channel_name),
|
|
||||||
LinkChannel {
|
|
||||||
to: path.channel_id(),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
items.push(ContextMenuItem::Separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
let expand_action_name = if self.is_channel_collapsed(&path) {
|
|
||||||
"Expand Subchannels"
|
|
||||||
} else {
|
} else {
|
||||||
"Collapse Subchannels"
|
"Select"
|
||||||
};
|
};
|
||||||
|
|
||||||
items.extend([
|
items.push(ContextMenuItem::action(
|
||||||
ContextMenuItem::action(
|
select_action_name,
|
||||||
|
ToggleSelectedIx { ix },
|
||||||
|
));
|
||||||
|
|
||||||
|
if self.has_subchannels(ix) {
|
||||||
|
let expand_action_name = if self.is_channel_collapsed(&path) {
|
||||||
|
"Expand Subchannels"
|
||||||
|
} else {
|
||||||
|
"Collapse Subchannels"
|
||||||
|
};
|
||||||
|
items.push(ContextMenuItem::action(
|
||||||
expand_action_name,
|
expand_action_name,
|
||||||
ToggleCollapse {
|
ToggleCollapse {
|
||||||
location: path.clone(),
|
location: path.clone(),
|
||||||
},
|
},
|
||||||
),
|
));
|
||||||
ContextMenuItem::action(
|
}
|
||||||
"Open Notes",
|
|
||||||
OpenChannelBuffer {
|
items.push(ContextMenuItem::action(
|
||||||
channel_id: path.channel_id(),
|
"Open Notes",
|
||||||
},
|
OpenChannelBuffer {
|
||||||
),
|
channel_id: path.channel_id(),
|
||||||
]);
|
},
|
||||||
|
));
|
||||||
|
|
||||||
if self.channel_store.read(cx).is_user_admin(path.channel_id()) {
|
if self.channel_store.read(cx).is_user_admin(path.channel_id()) {
|
||||||
let parent_id = path.parent_id();
|
let parent_id = path.parent_id();
|
||||||
|
@ -2212,13 +2353,38 @@ impl CollabPanel {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
items.extend([ContextMenuItem::action(
|
items.extend([
|
||||||
"Move this channel",
|
ContextMenuItem::action(
|
||||||
StartMoveChannel {
|
"Move this channel",
|
||||||
channel_id: path.channel_id(),
|
StartMoveChannelFor {
|
||||||
parent_id,
|
channel_id: path.channel_id(),
|
||||||
},
|
parent_id,
|
||||||
)]);
|
},
|
||||||
|
),
|
||||||
|
ContextMenuItem::action(
|
||||||
|
"Link this channel",
|
||||||
|
StartLinkChannelFor {
|
||||||
|
channel_id: path.channel_id(),
|
||||||
|
parent_id,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if let Some(channel_name) = channel_name {
|
||||||
|
items.push(ContextMenuItem::Separator);
|
||||||
|
items.push(ContextMenuItem::action(
|
||||||
|
format!("Move '#{}' here", channel_name),
|
||||||
|
MoveChannel {
|
||||||
|
to: path.channel_id(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
items.push(ContextMenuItem::action(
|
||||||
|
format!("Link '#{}' here", channel_name),
|
||||||
|
LinkChannel {
|
||||||
|
to: path.channel_id(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
items.extend([
|
items.extend([
|
||||||
ContextMenuItem::Separator,
|
ContextMenuItem::Separator,
|
||||||
|
@ -2598,7 +2764,7 @@ impl CollabPanel {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.deploy_channel_context_menu(None, &path.to_owned(), cx);
|
self.deploy_channel_context_menu(None, &path.to_owned(), self.selection.unwrap(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_channel(&self) -> Option<(&Arc<Channel>, &ChannelPath)> {
|
fn selected_channel(&self) -> Option<(&Arc<Channel>, &ChannelPath)> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue