diff --git a/assets/themes/cave-dark.json b/assets/themes/cave-dark.json index 3942296d48..ab75d12b64 100644 --- a/assets/themes/cave-dark.json +++ b/assets/themes/cave-dark.json @@ -90,6 +90,10 @@ }, "workspace": { "background": "#26232a", + "joining_project_avatar": { + "corner_radius": 40, + "width": 80 + }, "joining_project_message": { "padding": 12, "family": "Zed Sans", diff --git a/assets/themes/cave-light.json b/assets/themes/cave-light.json index cdf29c27e4..543abffeb4 100644 --- a/assets/themes/cave-light.json +++ b/assets/themes/cave-light.json @@ -90,6 +90,10 @@ }, "workspace": { "background": "#e2dfe7", + "joining_project_avatar": { + "corner_radius": 40, + "width": 80 + }, "joining_project_message": { "padding": 12, "family": "Zed Sans", diff --git a/assets/themes/dark.json b/assets/themes/dark.json index 80423cc5da..bdb38dbe0e 100644 --- a/assets/themes/dark.json +++ b/assets/themes/dark.json @@ -90,6 +90,10 @@ }, "workspace": { "background": "#1c1c1c", + "joining_project_avatar": { + "corner_radius": 40, + "width": 80 + }, "joining_project_message": { "padding": 12, "family": "Zed Sans", diff --git a/assets/themes/light.json b/assets/themes/light.json index 1651d4209c..9667422894 100644 --- a/assets/themes/light.json +++ b/assets/themes/light.json @@ -90,6 +90,10 @@ }, "workspace": { "background": "#f8f8f8", + "joining_project_avatar": { + "corner_radius": 40, + "width": 80 + }, "joining_project_message": { "padding": 12, "family": "Zed Sans", diff --git a/assets/themes/solarized-dark.json b/assets/themes/solarized-dark.json index a60d59b85c..e4bc45ab4f 100644 --- a/assets/themes/solarized-dark.json +++ b/assets/themes/solarized-dark.json @@ -90,6 +90,10 @@ }, "workspace": { "background": "#073642", + "joining_project_avatar": { + "corner_radius": 40, + "width": 80 + }, "joining_project_message": { "padding": 12, "family": "Zed Sans", diff --git a/assets/themes/solarized-light.json b/assets/themes/solarized-light.json index 2e4224c2b1..898b7e62a1 100644 --- a/assets/themes/solarized-light.json +++ b/assets/themes/solarized-light.json @@ -90,6 +90,10 @@ }, "workspace": { "background": "#eee8d5", + "joining_project_avatar": { + "corner_radius": 40, + "width": 80 + }, "joining_project_message": { "padding": 12, "family": "Zed Sans", diff --git a/assets/themes/sulphurpool-dark.json b/assets/themes/sulphurpool-dark.json index 5fd14a46cf..9a8e897d57 100644 --- a/assets/themes/sulphurpool-dark.json +++ b/assets/themes/sulphurpool-dark.json @@ -90,6 +90,10 @@ }, "workspace": { "background": "#293256", + "joining_project_avatar": { + "corner_radius": 40, + "width": 80 + }, "joining_project_message": { "padding": 12, "family": "Zed Sans", diff --git a/assets/themes/sulphurpool-light.json b/assets/themes/sulphurpool-light.json index 65aaab6246..d3a017ca06 100644 --- a/assets/themes/sulphurpool-light.json +++ b/assets/themes/sulphurpool-light.json @@ -90,6 +90,10 @@ }, "workspace": { "background": "#dfe2f1", + "joining_project_avatar": { + "corner_radius": 40, + "width": 80 + }, "joining_project_message": { "padding": 12, "family": "Zed Sans", diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 9ea62f0831..a33739293f 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -336,14 +336,14 @@ impl ContactsPanel { fn render_contact_project( contact: Arc, current_user_id: Option, - project_ix: usize, + project_index: usize, app_state: Arc, theme: &theme::ContactsPanel, is_last_project: bool, is_selected: bool, cx: &mut LayoutContext, ) -> ElementBox { - let project = &contact.projects[project_ix]; + let project = &contact.projects[project_index]; let project_id = project.id; let is_host = Some(contact.user.id) == current_user_id; let is_guest = !is_host @@ -445,7 +445,8 @@ impl ContactsPanel { .on_click(move |_, cx| { if !is_host && !is_guest { cx.dispatch_global_action(JoinProject { - project_id, + contact: contact.clone(), + project_index, app_state: app_state.clone(), }); } @@ -768,12 +769,12 @@ impl ContactsPanel { let section = *section; self.toggle_expanded(&ToggleExpanded(section), cx); } - ContactEntry::ContactProject(contact, project_ix) => { - cx.dispatch_global_action(JoinProject { - project_id: contact.projects[*project_ix].id, + ContactEntry::ContactProject(contact, project_index) => cx + .dispatch_global_action(JoinProject { + contact: contact.clone(), + project_index: *project_index, app_state: self.app_state.clone(), - }) - } + }), _ => {} } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index f663071002..f4b99f7b9e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -48,6 +48,7 @@ pub struct Workspace { pub modal: ContainerStyle, pub notification: ContainerStyle, pub notifications: Notifications, + pub joining_project_avatar: ImageStyle, pub joining_project_message: ContainedText, } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 85e03461a9..9849e14bba 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -8,7 +8,8 @@ mod toolbar; use anyhow::{anyhow, Context, Result}; use client::{ - proto, Authenticate, ChannelList, Client, PeerId, Subscription, TypedEnvelope, User, UserStore, + proto, Authenticate, ChannelList, Client, Contact, PeerId, Subscription, TypedEnvelope, User, + UserStore, }; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; @@ -97,7 +98,8 @@ pub struct ToggleFollow(pub PeerId); #[derive(Clone)] pub struct JoinProject { - pub project_id: u64, + pub contact: Arc, + pub project_index: usize, pub app_state: Arc, } @@ -117,7 +119,13 @@ pub fn init(client: &Arc, cx: &mut MutableAppContext) { open_new(&action.0, cx) }); cx.add_global_action(move |action: &JoinProject, cx: &mut MutableAppContext| { - join_project(action.project_id, &action.app_state, cx).detach(); + join_project( + action.contact.clone(), + action.project_index, + &action.app_state, + cx, + ) + .detach(); }); cx.add_async_action(Workspace::toggle_follow); @@ -2268,12 +2276,16 @@ pub fn open_paths( } pub fn join_project( - project_id: u64, + contact: Arc, + project_index: usize, app_state: &Arc, cx: &mut MutableAppContext, ) -> Task>> { + let project_id = contact.projects[project_index].id; + struct JoiningNotice { - message: &'static str, + avatar: Option>, + message: String, } impl Entity for JoiningNotice { @@ -2287,16 +2299,28 @@ pub fn join_project( fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let theme = &cx.global::().theme.workspace; - Text::new( - self.message.to_string(), - theme.joining_project_message.text.clone(), - ) - .contained() - .with_style(theme.joining_project_message.container) - .aligned() - .contained() - .with_background_color(theme.background) - .boxed() + + Flex::column() + .with_children(self.avatar.clone().map(|avatar| { + Image::new(avatar) + .with_style(theme.joining_project_avatar) + .aligned() + .boxed() + })) + .with_child( + Text::new( + self.message.clone(), + theme.joining_project_message.text.clone(), + ) + .contained() + .with_style(theme.joining_project_message.container) + .aligned() + .boxed(), + ) + .aligned() + .contained() + .with_background_color(theme.background) + .boxed() } } @@ -2312,7 +2336,12 @@ pub fn join_project( cx.spawn(|mut cx| async move { let (window, joining_notice) = cx.update(|cx| { cx.add_window((app_state.build_window_options)(), |_| JoiningNotice { - message: "Loading remote project...", + avatar: contact.user.avatar.clone(), + message: format!( + "Asking to join @{}'s copy of {}...", + contact.user.github_login, + humanize_list(&contact.projects[project_index].worktree_root_names) + ), }) }); let project = Project::remote( @@ -2338,15 +2367,22 @@ pub fn join_project( workspace })), Err(error @ _) => { + let login = &contact.user.github_login; let message = match error { project::JoinProjectError::HostDeclined => { - "The host declined your request to join." + format!("@{} declined your request.", login) } - project::JoinProjectError::HostClosedProject => "The host closed the project.", - project::JoinProjectError::HostWentOffline => "The host went offline.", - project::JoinProjectError::Other(_) => { - "An error occurred when attempting to join the project." + project::JoinProjectError::HostClosedProject => { + format!( + "@{} closed their copy of {}.", + login, + humanize_list(&contact.projects[project_index].worktree_root_names) + ) } + project::JoinProjectError::HostWentOffline => { + format!("@{} went offline.", login) + } + project::JoinProjectError::Other(_) => "An error occurred.".to_string(), }; joining_notice.update(cx, |notice, cx| { notice.message = message; @@ -2372,3 +2408,18 @@ fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { }); cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone())); } + +fn humanize_list<'a>(items: impl IntoIterator) -> String { + let mut list = String::new(); + let mut items = items.into_iter().enumerate().peekable(); + while let Some((ix, item)) = items.next() { + if ix > 0 { + list.push_str(", "); + } + if items.peek().is_none() { + list.push_str("and "); + } + list.push_str(item); + } + list +} diff --git a/script/watch-themes b/script/watch-themes new file mode 100755 index 0000000000..a3663962f7 --- /dev/null +++ b/script/watch-themes @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +cd styles +npm install +npm run watch diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 7c17e4addd..1109068688 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -41,6 +41,10 @@ export default function workspace(theme: Theme) { return { background: backgroundColor(theme, 300), + joiningProjectAvatar: { + cornerRadius: 40, + width: 80, + }, joiningProjectMessage: { padding: 12, ...text(theme, "sans", "primary", { size: "lg" })