diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 4bb0860704..ccf17974a4 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -1,13 +1,16 @@ mod collab_titlebar_item; mod contacts_popover; mod incoming_call_notification; +mod project_shared_notification; -use client::UserStore; pub use collab_titlebar_item::CollabTitlebarItem; -use gpui::{ModelHandle, MutableAppContext}; +use gpui::MutableAppContext; +use std::sync::Arc; +use workspace::AppState; -pub fn init(user_store: ModelHandle, cx: &mut MutableAppContext) { +pub fn init(app_state: Arc, cx: &mut MutableAppContext) { contacts_popover::init(cx); collab_titlebar_item::init(cx); - incoming_call_notification::init(user_store, cx); + incoming_call_notification::init(app_state.user_store.clone(), cx); + project_shared_notification::init(app_state, cx); } diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs new file mode 100644 index 0000000000..879c3ca28c --- /dev/null +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -0,0 +1,174 @@ +use call::{room, ActiveCall}; +use client::User; +use gpui::{ + actions, + elements::*, + geometry::{rect::RectF, vector::vec2f}, + Entity, MouseButton, MutableAppContext, RenderContext, View, ViewContext, WindowBounds, + WindowKind, WindowOptions, +}; +use project::Project; +use settings::Settings; +use std::sync::Arc; +use workspace::{AppState, Workspace}; + +actions!(project_shared_notification, [JoinProject, DismissProject]); + +pub fn init(app_state: Arc, cx: &mut MutableAppContext) { + cx.add_action(ProjectSharedNotification::join); + cx.add_action(ProjectSharedNotification::dismiss); + + let active_call = ActiveCall::global(cx); + let mut _room_subscription = None; + cx.observe(&active_call, move |active_call, cx| { + if let Some(room) = active_call.read(cx).room().cloned() { + let app_state = app_state.clone(); + _room_subscription = Some(cx.subscribe(&room, move |_, event, cx| match event { + room::Event::RemoteProjectShared { owner, project_id } => { + cx.add_window( + WindowOptions { + bounds: WindowBounds::Fixed(RectF::new( + vec2f(0., 0.), + vec2f(300., 400.), + )), + titlebar: None, + center: true, + kind: WindowKind::PopUp, + is_movable: false, + }, + |_| { + ProjectSharedNotification::new( + *project_id, + owner.clone(), + app_state.clone(), + ) + }, + ); + } + })); + } else { + _room_subscription = None; + } + }) + .detach(); +} + +pub struct ProjectSharedNotification { + project_id: u64, + owner: Arc, + app_state: Arc, +} + +impl ProjectSharedNotification { + fn new(project_id: u64, owner: Arc, app_state: Arc) -> Self { + Self { + project_id, + owner, + app_state, + } + } + + fn join(&mut self, _: &JoinProject, cx: &mut ViewContext) { + let project_id = self.project_id; + let app_state = self.app_state.clone(); + cx.spawn_weak(|_, mut cx| async move { + let project = Project::remote( + project_id, + app_state.client.clone(), + app_state.user_store.clone(), + app_state.project_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + cx.clone(), + ) + .await?; + + cx.add_window((app_state.build_window_options)(), |cx| { + let mut workspace = Workspace::new(project, app_state.default_item_factory, cx); + (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + workspace + }); + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + + let window_id = cx.window_id(); + cx.remove_window(window_id); + } + + fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext) { + let window_id = cx.window_id(); + cx.remove_window(window_id); + } + + fn render_owner(&self, cx: &mut RenderContext) -> ElementBox { + let theme = &cx.global::().theme.project_shared_notification; + Flex::row() + .with_children( + self.owner + .avatar + .clone() + .map(|avatar| Image::new(avatar).with_style(theme.owner_avatar).boxed()), + ) + .with_child( + Label::new( + format!("{} has shared a new project", self.owner.github_login), + theme.message.text.clone(), + ) + .boxed(), + ) + .boxed() + } + + fn render_buttons(&self, cx: &mut RenderContext) -> ElementBox { + enum Join {} + enum Dismiss {} + + Flex::row() + .with_child( + MouseEventHandler::::new(0, cx, |_, cx| { + let theme = &cx.global::().theme.project_shared_notification; + Label::new("Join".to_string(), theme.join_button.text.clone()) + .contained() + .with_style(theme.join_button.container) + .boxed() + }) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(JoinProject); + }) + .boxed(), + ) + .with_child( + MouseEventHandler::::new(0, cx, |_, cx| { + let theme = &cx.global::().theme.project_shared_notification; + Label::new("Dismiss".to_string(), theme.dismiss_button.text.clone()) + .contained() + .with_style(theme.dismiss_button.container) + .boxed() + }) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(DismissProject); + }) + .boxed(), + ) + .boxed() + } +} + +impl Entity for ProjectSharedNotification { + type Event = (); +} + +impl View for ProjectSharedNotification { + fn ui_name() -> &'static str { + "ProjectSharedNotification" + } + + fn render(&mut self, cx: &mut RenderContext) -> gpui::ElementBox { + Flex::row() + .with_child(self.render_owner(cx)) + .with_child(self.render_buttons(cx)) + .boxed() + } +} diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 52b34462aa..6253b6dabb 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -30,6 +30,7 @@ pub struct Theme { pub breadcrumbs: ContainedText, pub contact_notification: ContactNotification, pub update_notification: UpdateNotification, + pub project_shared_notification: ProjectSharedNotification, pub tooltip: TooltipStyle, pub terminal: TerminalStyle, } @@ -481,6 +482,14 @@ pub struct UpdateNotification { pub dismiss_button: Interactive, } +#[derive(Deserialize, Default)] +pub struct ProjectSharedNotification { + pub owner_avatar: ImageStyle, + pub message: ContainedText, + pub join_button: ContainedText, + pub dismiss_button: ContainedText, +} + #[derive(Clone, Deserialize, Default)] pub struct Editor { pub text_color: Color, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ea42c61dfb..580493f6d0 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -107,7 +107,6 @@ fn main() { project::Project::init(&client); client::Channel::init(&client); client::init(client.clone(), cx); - collab_ui::init(user_store.clone(), cx); command_palette::init(cx); editor::init(cx); go_to_line::init(cx); @@ -157,6 +156,7 @@ fn main() { journal::init(app_state.clone(), cx); theme_selector::init(app_state.clone(), cx); zed::init(&app_state, cx); + collab_ui::init(app_state.clone(), cx); cx.set_menus(menus::menus()); diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index a3ab4b654c..1c0c81cfde 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -14,6 +14,7 @@ import contextMenu from "./contextMenu"; import projectDiagnostics from "./projectDiagnostics"; import contactNotification from "./contactNotification"; import updateNotification from "./updateNotification"; +import projectSharedNotification from "./projectSharedNotification"; import tooltip from "./tooltip"; import terminal from "./terminal"; @@ -47,6 +48,7 @@ export default function app(theme: Theme): Object { }, contactNotification: contactNotification(theme), updateNotification: updateNotification(theme), + projectSharedNotification: projectSharedNotification(theme), tooltip: tooltip(theme), terminal: terminal(theme), }; diff --git a/styles/src/styleTree/projectSharedNotification.ts b/styles/src/styleTree/projectSharedNotification.ts new file mode 100644 index 0000000000..bc34265135 --- /dev/null +++ b/styles/src/styleTree/projectSharedNotification.ts @@ -0,0 +1,22 @@ +import Theme from "../themes/common/theme"; +import { text } from "./components"; + +export default function projectSharedNotification(theme: Theme): Object { + const avatarSize = 12; + return { + ownerAvatar: { + height: avatarSize, + width: avatarSize, + cornerRadius: 6, + }, + message: { + ...text(theme, "sans", "primary", { size: "xs" }), + }, + joinButton: { + ...text(theme, "sans", "primary", { size: "xs" }) + }, + dismissButton: { + ...text(theme, "sans", "primary", { size: "xs" }) + }, + }; +}