Detailed changes
@@ -47,5 +47,6 @@ tempfile = "3"
collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
+client = { path = "../client", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
@@ -111,6 +111,10 @@ impl ChannelStore {
}
}
+ pub fn client(&self) -> Arc<Client> {
+ self.client.clone()
+ }
+
pub fn has_children(&self, channel_id: ChannelId) -> bool {
self.channel_paths.iter().any(|path| {
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
@@ -4,15 +4,12 @@ use super::*;
use client::{test::FakeServer, Client, UserStore};
use gpui::{AppContext, ModelHandle, TestAppContext};
use rpc::proto;
+use settings::SettingsStore;
use util::http::FakeHttpClient;
#[gpui::test]
fn test_update_channels(cx: &mut AppContext) {
- let http = FakeHttpClient::with_404_response();
- let client = Client::new(http.clone(), cx);
- let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
-
- let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx));
+ let channel_store = init_test(cx);
update_channels(
&channel_store,
@@ -80,11 +77,7 @@ fn test_update_channels(cx: &mut AppContext) {
#[gpui::test]
fn test_dangling_channel_paths(cx: &mut AppContext) {
- let http = FakeHttpClient::with_404_response();
- let client = Client::new(http.clone(), cx);
- let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
-
- let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx));
+ let channel_store = init_test(cx);
update_channels(
&channel_store,
@@ -141,18 +134,11 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
#[gpui::test]
async fn test_channel_messages(cx: &mut TestAppContext) {
- cx.foreground().forbid_parking();
-
let user_id = 5;
- let http_client = FakeHttpClient::with_404_response();
- let client = cx.update(|cx| Client::new(http_client.clone(), cx));
- let server = FakeServer::for_client(user_id, &client, cx).await;
- let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
- crate::init(&client);
-
- let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx));
-
let channel_id = 5;
+ let channel_store = cx.update(init_test);
+ let client = channel_store.read_with(cx, |s, _| s.client());
+ let server = FakeServer::for_client(user_id, &client, cx).await;
// Get the available channels.
server.send(proto::UpdateChannels {
@@ -163,85 +149,71 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
}],
..Default::default()
});
- channel_store.next_notification(cx).await;
+ cx.foreground().run_until_parked();
cx.read(|cx| {
assert_channels(&channel_store, &[(0, "the-channel".to_string(), false)], cx);
});
let get_users = server.receive::<proto::GetUsers>().await.unwrap();
assert_eq!(get_users.payload.user_ids, vec![5]);
- server
- .respond(
- get_users.receipt(),
- proto::UsersResponse {
- users: vec![proto::User {
- id: 5,
- github_login: "nathansobo".into(),
- avatar_url: "http://avatar.com/nathansobo".into(),
- }],
- },
- )
- .await;
+ server.respond(
+ get_users.receipt(),
+ proto::UsersResponse {
+ users: vec![proto::User {
+ id: 5,
+ github_login: "nathansobo".into(),
+ avatar_url: "http://avatar.com/nathansobo".into(),
+ }],
+ },
+ );
// Join a channel and populate its existing messages.
- let channel = channel_store
- .update(cx, |store, cx| {
- let channel_id = store.channels().next().unwrap().1.id;
- store.open_channel_chat(channel_id, cx)
- })
- .await
- .unwrap();
- channel.read_with(cx, |channel, _| assert!(channel.messages().is_empty()));
+ let channel = channel_store.update(cx, |store, cx| {
+ let channel_id = store.channels().next().unwrap().1.id;
+ store.open_channel_chat(channel_id, cx)
+ });
let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
- server
- .respond(
- join_channel.receipt(),
- proto::JoinChannelChatResponse {
- messages: vec![
- proto::ChannelMessage {
- id: 10,
- body: "a".into(),
- timestamp: 1000,
- sender_id: 5,
- nonce: Some(1.into()),
- },
- proto::ChannelMessage {
- id: 11,
- body: "b".into(),
- timestamp: 1001,
- sender_id: 6,
- nonce: Some(2.into()),
- },
- ],
- done: false,
- },
- )
- .await;
+ server.respond(
+ join_channel.receipt(),
+ proto::JoinChannelChatResponse {
+ messages: vec![
+ proto::ChannelMessage {
+ id: 10,
+ body: "a".into(),
+ timestamp: 1000,
+ sender_id: 5,
+ nonce: Some(1.into()),
+ },
+ proto::ChannelMessage {
+ id: 11,
+ body: "b".into(),
+ timestamp: 1001,
+ sender_id: 6,
+ nonce: Some(2.into()),
+ },
+ ],
+ done: false,
+ },
+ );
+
+ cx.foreground().start_waiting();
// Client requests all users for the received messages
let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
get_users.payload.user_ids.sort();
assert_eq!(get_users.payload.user_ids, vec![6]);
- server
- .respond(
- get_users.receipt(),
- proto::UsersResponse {
- users: vec![proto::User {
- id: 6,
- github_login: "maxbrunsfeld".into(),
- avatar_url: "http://avatar.com/maxbrunsfeld".into(),
- }],
- },
- )
- .await;
-
- assert_eq!(
- channel.next_event(cx).await,
- ChannelChatEvent::MessagesUpdated {
- old_range: 0..0,
- new_count: 2,
- }
+ server.respond(
+ get_users.receipt(),
+ proto::UsersResponse {
+ users: vec![proto::User {
+ id: 6,
+ github_login: "maxbrunsfeld".into(),
+ avatar_url: "http://avatar.com/maxbrunsfeld".into(),
+ }],
+ },
);
+
+ let channel = channel.await.unwrap();
channel.read_with(cx, |channel, _| {
assert_eq!(
channel
@@ -270,18 +242,16 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
// Client requests user for message since they haven't seen them yet
let get_users = server.receive::<proto::GetUsers>().await.unwrap();
assert_eq!(get_users.payload.user_ids, vec![7]);
- server
- .respond(
- get_users.receipt(),
- proto::UsersResponse {
- users: vec![proto::User {
- id: 7,
- github_login: "as-cii".into(),
- avatar_url: "http://avatar.com/as-cii".into(),
- }],
- },
- )
- .await;
+ server.respond(
+ get_users.receipt(),
+ proto::UsersResponse {
+ users: vec![proto::User {
+ id: 7,
+ github_login: "as-cii".into(),
+ avatar_url: "http://avatar.com/as-cii".into(),
+ }],
+ },
+ );
assert_eq!(
channel.next_event(cx).await,
@@ -307,30 +277,28 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
assert_eq!(get_messages.payload.channel_id, 5);
assert_eq!(get_messages.payload.before_message_id, 10);
- server
- .respond(
- get_messages.receipt(),
- proto::GetChannelMessagesResponse {
- done: true,
- messages: vec![
- proto::ChannelMessage {
- id: 8,
- body: "y".into(),
- timestamp: 998,
- sender_id: 5,
- nonce: Some(4.into()),
- },
- proto::ChannelMessage {
- id: 9,
- body: "z".into(),
- timestamp: 999,
- sender_id: 6,
- nonce: Some(5.into()),
- },
- ],
- },
- )
- .await;
+ server.respond(
+ get_messages.receipt(),
+ proto::GetChannelMessagesResponse {
+ done: true,
+ messages: vec![
+ proto::ChannelMessage {
+ id: 8,
+ body: "y".into(),
+ timestamp: 998,
+ sender_id: 5,
+ nonce: Some(4.into()),
+ },
+ proto::ChannelMessage {
+ id: 9,
+ body: "z".into(),
+ timestamp: 999,
+ sender_id: 6,
+ nonce: Some(5.into()),
+ },
+ ],
+ },
+ );
assert_eq!(
channel.next_event(cx).await,
@@ -353,6 +321,19 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
});
}
+fn init_test(cx: &mut AppContext) -> ModelHandle<ChannelStore> {
+ let http = FakeHttpClient::with_404_response();
+ let client = Client::new(http.clone(), cx);
+ let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
+
+ cx.foreground().forbid_parking();
+ cx.set_global(SettingsStore::test(cx));
+ crate::init(&client);
+ client::init(&client, cx);
+
+ cx.add_model(|cx| ChannelStore::new(client, user_store, cx))
+}
+
fn update_channels(
channel_store: &ModelHandle<ChannelStore>,
message: proto::UpdateChannels,
@@ -170,8 +170,7 @@ impl FakeServer {
staff: false,
flags: Default::default(),
},
- )
- .await;
+ );
continue;
}
@@ -182,11 +181,7 @@ impl FakeServer {
}
}
- pub async fn respond<T: proto::RequestMessage>(
- &self,
- receipt: Receipt<T>,
- response: T::Response,
- ) {
+ pub fn respond<T: proto::RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) {
self.peer.respond(receipt, response).unwrap()
}
@@ -82,7 +82,7 @@ impl Database {
id: row.id.to_proto(),
sender_id: row.sender_id.to_proto(),
body: row.body,
- timestamp: row.sent_at.unix_timestamp() as u64,
+ timestamp: row.sent_at.assume_utc().unix_timestamp() as u64,
nonce: Some(proto::Nonce {
upper_half: nonce.0,
lower_half: nonce.1,
@@ -124,6 +124,9 @@ impl Database {
Err(anyhow!("not a chat participant"))?;
}
+ let timestamp = timestamp.to_offset(time::UtcOffset::UTC);
+ let timestamp = time::PrimitiveDateTime::new(timestamp.date(), timestamp.time());
+
let message = channel_message::Entity::insert(channel_message::ActiveModel {
channel_id: ActiveValue::Set(channel_id),
sender_id: ActiveValue::Set(user_id),
@@ -1,6 +1,6 @@
use crate::db::{ChannelId, MessageId, UserId};
use sea_orm::entity::prelude::*;
-use time::OffsetDateTime;
+use time::PrimitiveDateTime;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "channel_messages")]
@@ -10,7 +10,7 @@ pub struct Model {
pub channel_id: ChannelId,
pub sender_id: UserId,
pub body: String,
- pub sent_at: OffsetDateTime,
+ pub sent_at: PrimitiveDateTime,
pub nonce: Uuid,
}
@@ -30,6 +30,12 @@ async fn test_channel_message_nonces(db: &Arc<Database>) {
.await
.unwrap();
+ let owner_id = db.create_server("test").await.unwrap().0 as u32;
+
+ db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 0 }, user)
+ .await
+ .unwrap();
+
let msg1_id = db
.create_channel_message(channel, user, "1", OffsetDateTime::now_utc(), 1)
.await
@@ -1,21 +1,33 @@
+use crate::collab_panel::{CollaborationPanelDockPosition, CollaborationPanelSettings};
+use anyhow::Result;
use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelStore};
use client::Client;
+use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use gpui::{
actions,
elements::*,
platform::{CursorStyle, MouseButton},
+ serde_json,
views::{ItemType, Select, SelectStyle},
- AnyViewHandle, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
+ AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View,
+ ViewContext, ViewHandle, WeakViewHandle,
};
use language::language_settings::SoftWrap;
use menu::Confirm;
+use project::Fs;
+use serde::{Deserialize, Serialize};
use std::sync::Arc;
use theme::Theme;
use time::{OffsetDateTime, UtcOffset};
use util::{ResultExt, TryFutureExt};
+use workspace::{
+ dock::{DockPosition, Panel},
+ Workspace,
+};
const MESSAGE_LOADING_THRESHOLD: usize = 50;
+const CHAT_PANEL_KEY: &'static str = "ChatPanel";
pub struct ChatPanel {
client: Arc<Client>,
@@ -25,11 +37,25 @@ pub struct ChatPanel {
input_editor: ViewHandle<Editor>,
channel_select: ViewHandle<Select>,
local_timezone: UtcOffset,
+ fs: Arc<dyn Fs>,
+ width: Option<f32>,
+ pending_serialization: Task<Option<()>>,
+ has_focus: bool,
+}
+
+#[derive(Serialize, Deserialize)]
+struct SerializedChatPanel {
+ width: Option<f32>,
}
-pub enum Event {}
+#[derive(Debug)]
+pub enum Event {
+ DockPositionChanged,
+ Focus,
+ Dismissed,
+}
-actions!(chat_panel, [LoadMoreMessages]);
+actions!(chat_panel, [LoadMoreMessages, ToggleFocus]);
pub fn init(cx: &mut AppContext) {
cx.add_action(ChatPanel::send);
@@ -37,11 +63,11 @@ pub fn init(cx: &mut AppContext) {
}
impl ChatPanel {
- pub fn new(
- rpc: Arc<Client>,
- channel_list: ModelHandle<ChannelStore>,
- cx: &mut ViewContext<Self>,
- ) -> Self {
+ pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
+ let fs = workspace.app_state().fs.clone();
+ let client = workspace.app_state().client.clone();
+ let channel_store = workspace.app_state().channel_store.clone();
+
let input_editor = cx.add_view(|cx| {
let mut editor = Editor::auto_height(
4,
@@ -51,12 +77,13 @@ impl ChatPanel {
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor
});
+
let channel_select = cx.add_view(|cx| {
- let channel_list = channel_list.clone();
+ let channel_store = channel_store.clone();
Select::new(0, cx, {
move |ix, item_type, is_hovered, cx| {
Self::render_channel_name(
- &channel_list,
+ &channel_store,
ix,
item_type,
is_hovered,
@@ -85,45 +112,97 @@ impl ChatPanel {
}
});
- let mut this = Self {
- client: rpc,
- channel_store: channel_list,
- active_channel: Default::default(),
- message_list,
- input_editor,
- channel_select,
- local_timezone: cx.platform().local_timezone(),
- };
+ cx.add_view(|cx| {
+ let mut this = Self {
+ fs,
+ client,
+ channel_store,
+ active_channel: Default::default(),
+ pending_serialization: Task::ready(None),
+ message_list,
+ input_editor,
+ channel_select,
+ local_timezone: cx.platform().local_timezone(),
+ has_focus: false,
+ width: None,
+ };
- this.init_active_channel(cx);
- cx.observe(&this.channel_store, |this, _, cx| {
this.init_active_channel(cx);
- })
- .detach();
-
- cx.observe(&this.channel_select, |this, channel_select, cx| {
- let selected_ix = channel_select.read(cx).selected_index();
- let selected_channel_id = this
- .channel_store
- .read(cx)
- .channel_at_index(selected_ix)
- .map(|e| e.1.id);
- if let Some(selected_channel_id) = selected_channel_id {
- let open_chat = this.channel_store.update(cx, |store, cx| {
- store.open_channel_chat(selected_channel_id, cx)
- });
- cx.spawn(|this, mut cx| async move {
- let chat = open_chat.await?;
- this.update(&mut cx, |this, cx| {
- this.set_active_channel(chat, cx);
+ cx.observe(&this.channel_store, |this, _, cx| {
+ this.init_active_channel(cx);
+ })
+ .detach();
+
+ cx.observe(&this.channel_select, |this, channel_select, cx| {
+ let selected_ix = channel_select.read(cx).selected_index();
+ let selected_channel_id = this
+ .channel_store
+ .read(cx)
+ .channel_at_index(selected_ix)
+ .map(|e| e.1.id);
+ if let Some(selected_channel_id) = selected_channel_id {
+ let open_chat = this.channel_store.update(cx, |store, cx| {
+ store.open_channel_chat(selected_channel_id, cx)
+ });
+ cx.spawn(|this, mut cx| async move {
+ let chat = open_chat.await?;
+ this.update(&mut cx, |this, cx| {
+ this.set_active_channel(chat, cx);
+ })
})
- })
- .detach_and_log_err(cx);
- }
+ .detach_and_log_err(cx);
+ }
+ })
+ .detach();
+
+ this
+ })
+ }
+
+ pub fn load(
+ workspace: WeakViewHandle<Workspace>,
+ cx: AsyncAppContext,
+ ) -> Task<Result<ViewHandle<Self>>> {
+ cx.spawn(|mut cx| async move {
+ let serialized_panel = if let Some(panel) = cx
+ .background()
+ .spawn(async move { KEY_VALUE_STORE.read_kvp(CHAT_PANEL_KEY) })
+ .await
+ .log_err()
+ .flatten()
+ {
+ Some(serde_json::from_str::<SerializedChatPanel>(&panel)?)
+ } else {
+ None
+ };
+
+ workspace.update(&mut cx, |workspace, cx| {
+ let panel = Self::new(workspace, cx);
+ if let Some(serialized_panel) = serialized_panel {
+ panel.update(cx, |panel, cx| {
+ panel.width = serialized_panel.width;
+ cx.notify();
+ });
+ }
+ panel
+ })
})
- .detach();
+ }
- this
+ fn serialize(&mut self, cx: &mut ViewContext<Self>) {
+ let width = self.width;
+ self.pending_serialization = cx.background().spawn(
+ async move {
+ KEY_VALUE_STORE
+ .write_kvp(
+ CHAT_PANEL_KEY.into(),
+ serde_json::to_string(&SerializedChatPanel { width })?,
+ )
+ .await?;
+ anyhow::Ok(())
+ }
+ .log_err(),
+ );
}
fn init_active_channel(&mut self, cx: &mut ViewContext<Self>) {
@@ -365,6 +444,68 @@ impl View for ChatPanel {
}
}
+impl Panel for ChatPanel {
+ fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
+ match settings::get::<CollaborationPanelSettings>(cx).dock {
+ CollaborationPanelDockPosition::Left => DockPosition::Left,
+ CollaborationPanelDockPosition::Right => DockPosition::Right,
+ }
+ }
+
+ fn position_is_valid(&self, position: DockPosition) -> bool {
+ matches!(position, DockPosition::Left | DockPosition::Right)
+ }
+
+ fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
+ settings::update_settings_file::<CollaborationPanelSettings>(
+ self.fs.clone(),
+ cx,
+ move |settings| {
+ let dock = match position {
+ DockPosition::Left | DockPosition::Bottom => {
+ CollaborationPanelDockPosition::Left
+ }
+ DockPosition::Right => CollaborationPanelDockPosition::Right,
+ };
+ settings.dock = Some(dock);
+ },
+ );
+ }
+
+ fn size(&self, cx: &gpui::WindowContext) -> f32 {
+ self.width
+ .unwrap_or_else(|| settings::get::<CollaborationPanelSettings>(cx).default_width)
+ }
+
+ fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+ self.width = size;
+ self.serialize(cx);
+ cx.notify();
+ }
+
+ fn icon_path(&self, cx: &gpui::WindowContext) -> Option<&'static str> {
+ settings::get::<CollaborationPanelSettings>(cx)
+ .button
+ .then(|| "icons/conversations.svg")
+ }
+
+ fn icon_tooltip(&self) -> (String, Option<Box<dyn gpui::Action>>) {
+ ("Chat Panel".to_string(), Some(Box::new(ToggleFocus)))
+ }
+
+ fn should_change_position_on_event(event: &Self::Event) -> bool {
+ matches!(event, Event::DockPositionChanged)
+ }
+
+ fn has_focus(&self, _cx: &gpui::WindowContext) -> bool {
+ self.has_focus
+ }
+
+ fn is_focus_event(event: &Self::Event) -> bool {
+ matches!(event, Event::Focus)
+ }
+}
+
fn format_timestamp(
mut timestamp: OffsetDateTime,
mut now: OffsetDateTime,
@@ -2,14 +2,18 @@ mod channel_modal;
mod contact_finder;
mod panel_settings;
+use crate::{
+ channel_view::{self, ChannelView},
+ face_pile::FacePile,
+};
use anyhow::Result;
use call::ActiveCall;
use channel::{Channel, ChannelEvent, ChannelId, ChannelStore};
+use channel_modal::ChannelModal;
use client::{proto::PeerId, Client, Contact, User, UserStore};
use context_menu::{ContextMenu, ContextMenuItem};
use db::kvp::KEY_VALUE_STORE;
use editor::{Cancel, Editor};
-
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
use futures::StreamExt;
use fuzzy::{match_strings, StringMatchCandidate};
@@ -31,7 +35,6 @@ use gpui::{
Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
-use panel_settings::{CollaborationPanelDockPosition, CollaborationPanelSettings};
use project::{Fs, Project};
use serde_derive::{Deserialize, Serialize};
use settings::SettingsStore;
@@ -44,11 +47,7 @@ use workspace::{
Workspace,
};
-use crate::{
- channel_view::{self, ChannelView},
- face_pile::FacePile,
-};
-use channel_modal::ChannelModal;
+pub use panel_settings::{CollaborationPanelDockPosition, CollaborationPanelSettings};
use self::contact_finder::ContactFinder;
@@ -113,7 +112,7 @@ impl_actions!(
const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
-pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
+pub fn init(cx: &mut AppContext) {
settings::register::<panel_settings::CollaborationPanelSettings>(cx);
contact_finder::init(cx);
channel_modal::init(cx);
@@ -32,7 +32,8 @@ actions!(
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
vcs_menu::init(cx);
collab_titlebar_item::init(cx);
- collab_panel::init(app_state.client.clone(), cx);
+ collab_panel::init(cx);
+ chat_panel::init(cx);
incoming_call_notification::init(&app_state, cx);
project_shared_notification::init(&app_state, cx);
sharing_status_indicator::init(cx);
@@ -214,6 +214,13 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
workspace.toggle_panel_focus::<collab_ui::collab_panel::CollabPanel>(cx);
},
);
+ cx.add_action(
+ |workspace: &mut Workspace,
+ _: &collab_ui::chat_panel::ToggleFocus,
+ cx: &mut ViewContext<Workspace>| {
+ workspace.toggle_panel_focus::<collab_ui::chat_panel::ChatPanel>(cx);
+ },
+ );
cx.add_action(
|workspace: &mut Workspace,
_: &terminal_panel::ToggleFocus,
@@ -338,11 +345,14 @@ pub fn initialize_workspace(
let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
let channels_panel =
collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
- let (project_panel, terminal_panel, assistant_panel, channels_panel) = futures::try_join!(
+ let chat_panel =
+ collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
+ let (project_panel, terminal_panel, assistant_panel, channels_panel, chat_panel) = futures::try_join!(
project_panel,
terminal_panel,
assistant_panel,
- channels_panel
+ channels_panel,
+ chat_panel,
)?;
workspace_handle.update(&mut cx, |workspace, cx| {
let project_panel_position = project_panel.position(cx);
@@ -362,6 +372,7 @@ pub fn initialize_workspace(
workspace.add_panel(terminal_panel, cx);
workspace.add_panel(assistant_panel, cx);
workspace.add_panel(channels_panel, cx);
+ workspace.add_panel(chat_panel, cx);
if !was_deserialized
&& workspace
@@ -12,6 +12,7 @@ import simple_message_notification from "./simple_message_notification"
import project_shared_notification from "./project_shared_notification"
import tooltip from "./tooltip"
import terminal from "./terminal"
+import chat_panel from "./chat_panel"
import collab_panel from "./collab_panel"
import toolbar_dropdown_menu from "./toolbar_dropdown_menu"
import incoming_call_notification from "./incoming_call_notification"
@@ -55,6 +56,7 @@ export default function app(): any {
terminal: terminal(),
assistant: assistant(),
feedback: feedback(),
+ chat_panel: chat_panel(),
component_test: component_test(),
}
}
@@ -0,0 +1,111 @@
+import {
+ background,
+ border,
+ border_color,
+ foreground,
+ text,
+} from "./components"
+import { interactive, toggleable } from "../element"
+import { useTheme } from "../theme"
+import collab_modals from "./collab_modals"
+import { icon_button, toggleable_icon_button } from "../component/icon_button"
+import { indicator } from "../component/indicator"
+
+export default function contacts_panel(): any {
+ const theme = useTheme()
+
+ const CHANNEL_SPACING = 4 as const
+ const NAME_MARGIN = 6 as const
+ const SPACING = 12 as const
+ const INDENT_SIZE = 8 as const
+ const ITEM_HEIGHT = 28 as const
+
+ const layer = theme.middle
+
+ const input_editor = {
+ background: background(layer, "on"),
+ corner_radius: 6,
+ text: text(layer, "sans", "base"),
+ placeholder_text: text(layer, "sans", "base", "disabled", {
+ size: "xs",
+ }),
+ selection: theme.players[0],
+ border: border(layer, "on"),
+ padding: {
+ bottom: 4,
+ left: 8,
+ right: 8,
+ top: 4,
+ },
+ margin: {
+ left: SPACING,
+ right: SPACING,
+ },
+ }
+
+ const channel_name = {
+ padding: {
+ top: 4,
+ bottom: 4,
+ left: 4,
+ right: 4,
+ },
+ hash: {
+ ...text(layer, "sans", "base"),
+ },
+ name: text(layer, "sans", "base"),
+ }
+
+ return {
+ background: background(layer),
+ channel_select: {
+ header: channel_name,
+ item: channel_name,
+ active_item: channel_name,
+ hovered_item: channel_name,
+ hovered_active_item: channel_name,
+ menu: {
+ padding: {
+ top: 10,
+ bottom: 10,
+ }
+ }
+ },
+ input_editor,
+ message: {
+ body: text(layer, "sans", "base"),
+ sender: {
+ padding: {
+ left: 4,
+ right: 4,
+ },
+ ...text(layer, "sans", "base", "disabled"),
+ },
+ timestamp: text(layer, "sans", "base"),
+ },
+ pending_message: {
+ body: text(layer, "sans", "base"),
+ sender: {
+ padding: {
+ left: 4,
+ right: 4,
+ },
+ ...text(layer, "sans", "base", "disabled"),
+ },
+ timestamp: text(layer, "sans", "base"),
+ },
+ sign_in_prompt: {
+ default: text(layer, "sans", "base"),
+ },
+ timestamp: {
+ body: text(layer, "sans", "base"),
+ sender: {
+ padding: {
+ left: 4,
+ right: 4,
+ },
+ ...text(layer, "sans", "base", "disabled"),
+ }
+ }
+ }
+}