diff --git a/assets/themes/cave-dark.json b/assets/themes/cave-dark.json index 3ced053621..e8d03c60e0 100644 --- a/assets/themes/cave-dark.json +++ b/assets/themes/cave-dark.json @@ -474,6 +474,21 @@ "notification": { "margin": { "top": 10 + }, + "background": "#26232a", + "corner_radius": 6, + "padding": 12, + "border": { + "color": "#19171c", + "width": 1 + }, + "shadow": { + "blur": 16, + "color": "#0000003d", + "offset": [ + 0, + 2 + ] } }, "notifications": { @@ -481,8 +496,7 @@ "margin": { "right": 10, "bottom": 10 - }, - "background": "#ff0000" + } } }, "editor": { @@ -1659,5 +1673,48 @@ "padding": { "left": 6 } + }, + "incoming_request_notification": { + "header_avatar": { + "height": 12, + "width": 12, + "corner_radius": 6 + }, + "header_message": { + "family": "Zed Sans", + "color": "#e2dfe7", + "size": 12, + "margin": { + "left": 4 + } + }, + "header_height": 18, + "body_message": { + "family": "Zed Sans", + "color": "#8b8792", + "size": 12, + "margin": { + "top": 6, + "bottom": 6 + } + }, + "button": { + "family": "Zed Sans", + "color": "#e2dfe7", + "size": 12, + "background": "#19171c", + "padding": 4, + "corner_radius": 6, + "margin": { + "left": 6 + } + }, + "dismiss_button": { + "color": "#8b8792", + "icon_width": 8, + "icon_height": 8, + "button_width": 8, + "button_height": 8 + } } } \ No newline at end of file diff --git a/assets/themes/cave-light.json b/assets/themes/cave-light.json index a0a19149df..de7de76670 100644 --- a/assets/themes/cave-light.json +++ b/assets/themes/cave-light.json @@ -474,6 +474,21 @@ "notification": { "margin": { "top": 10 + }, + "background": "#e2dfe7", + "corner_radius": 6, + "padding": 12, + "border": { + "color": "#efecf4", + "width": 1 + }, + "shadow": { + "blur": 16, + "color": "#0000001f", + "offset": [ + 0, + 2 + ] } }, "notifications": { @@ -481,8 +496,7 @@ "margin": { "right": 10, "bottom": 10 - }, - "background": "#ff0000" + } } }, "editor": { @@ -1659,5 +1673,48 @@ "padding": { "left": 6 } + }, + "incoming_request_notification": { + "header_avatar": { + "height": 12, + "width": 12, + "corner_radius": 6 + }, + "header_message": { + "family": "Zed Sans", + "color": "#26232a", + "size": 12, + "margin": { + "left": 4 + } + }, + "header_height": 18, + "body_message": { + "family": "Zed Sans", + "color": "#585260", + "size": 12, + "margin": { + "top": 6, + "bottom": 6 + } + }, + "button": { + "family": "Zed Sans", + "color": "#26232a", + "size": 12, + "background": "#efecf4", + "padding": 4, + "corner_radius": 6, + "margin": { + "left": 6 + } + }, + "dismiss_button": { + "color": "#585260", + "icon_width": 8, + "icon_height": 8, + "button_width": 8, + "button_height": 8 + } } } \ No newline at end of file diff --git a/assets/themes/dark.json b/assets/themes/dark.json index cbff53933e..78fa14aa5d 100644 --- a/assets/themes/dark.json +++ b/assets/themes/dark.json @@ -474,6 +474,21 @@ "notification": { "margin": { "top": 10 + }, + "background": "#1c1c1c", + "corner_radius": 6, + "padding": 12, + "border": { + "color": "#070707", + "width": 1 + }, + "shadow": { + "blur": 16, + "color": "#00000052", + "offset": [ + 0, + 2 + ] } }, "notifications": { @@ -481,8 +496,7 @@ "margin": { "right": 10, "bottom": 10 - }, - "background": "#ff0000" + } } }, "editor": { @@ -1659,5 +1673,48 @@ "padding": { "left": 6 } + }, + "incoming_request_notification": { + "header_avatar": { + "height": 12, + "width": 12, + "corner_radius": 6 + }, + "header_message": { + "family": "Zed Sans", + "color": "#f1f1f1", + "size": 12, + "margin": { + "left": 4 + } + }, + "header_height": 18, + "body_message": { + "family": "Zed Sans", + "color": "#9c9c9c", + "size": 12, + "margin": { + "top": 6, + "bottom": 6 + } + }, + "button": { + "family": "Zed Sans", + "color": "#f1f1f1", + "size": 12, + "background": "#0e0e0e80", + "padding": 4, + "corner_radius": 6, + "margin": { + "left": 6 + } + }, + "dismiss_button": { + "color": "#9c9c9c", + "icon_width": 8, + "icon_height": 8, + "button_width": 8, + "button_height": 8 + } } } \ No newline at end of file diff --git a/assets/themes/light.json b/assets/themes/light.json index c7331a083b..61923bfec5 100644 --- a/assets/themes/light.json +++ b/assets/themes/light.json @@ -474,6 +474,21 @@ "notification": { "margin": { "top": 10 + }, + "background": "#f8f8f8", + "corner_radius": 6, + "padding": 12, + "border": { + "color": "#d5d5d5", + "width": 1 + }, + "shadow": { + "blur": 16, + "color": "#0000001f", + "offset": [ + 0, + 2 + ] } }, "notifications": { @@ -481,8 +496,7 @@ "margin": { "right": 10, "bottom": 10 - }, - "background": "#ff0000" + } } }, "editor": { @@ -1659,5 +1673,48 @@ "padding": { "left": 6 } + }, + "incoming_request_notification": { + "header_avatar": { + "height": 12, + "width": 12, + "corner_radius": 6 + }, + "header_message": { + "family": "Zed Sans", + "color": "#2b2b2b", + "size": 12, + "margin": { + "left": 4 + } + }, + "header_height": 18, + "body_message": { + "family": "Zed Sans", + "color": "#474747", + "size": 12, + "margin": { + "top": 6, + "bottom": 6 + } + }, + "button": { + "family": "Zed Sans", + "color": "#2b2b2b", + "size": 12, + "background": "#f1f1f1", + "padding": 4, + "corner_radius": 6, + "margin": { + "left": 6 + } + }, + "dismiss_button": { + "color": "#717171", + "icon_width": 8, + "icon_height": 8, + "button_width": 8, + "button_height": 8 + } } } \ No newline at end of file diff --git a/assets/themes/solarized-dark.json b/assets/themes/solarized-dark.json index d3367a3a3f..9865c12586 100644 --- a/assets/themes/solarized-dark.json +++ b/assets/themes/solarized-dark.json @@ -474,6 +474,21 @@ "notification": { "margin": { "top": 10 + }, + "background": "#073642", + "corner_radius": 6, + "padding": 12, + "border": { + "color": "#002b36", + "width": 1 + }, + "shadow": { + "blur": 16, + "color": "#0000003d", + "offset": [ + 0, + 2 + ] } }, "notifications": { @@ -481,8 +496,7 @@ "margin": { "right": 10, "bottom": 10 - }, - "background": "#ff0000" + } } }, "editor": { @@ -1659,5 +1673,48 @@ "padding": { "left": 6 } + }, + "incoming_request_notification": { + "header_avatar": { + "height": 12, + "width": 12, + "corner_radius": 6 + }, + "header_message": { + "family": "Zed Sans", + "color": "#eee8d5", + "size": 12, + "margin": { + "left": 4 + } + }, + "header_height": 18, + "body_message": { + "family": "Zed Sans", + "color": "#93a1a1", + "size": 12, + "margin": { + "top": 6, + "bottom": 6 + } + }, + "button": { + "family": "Zed Sans", + "color": "#eee8d5", + "size": 12, + "background": "#002b36", + "padding": 4, + "corner_radius": 6, + "margin": { + "left": 6 + } + }, + "dismiss_button": { + "color": "#93a1a1", + "icon_width": 8, + "icon_height": 8, + "button_width": 8, + "button_height": 8 + } } } \ No newline at end of file diff --git a/assets/themes/solarized-light.json b/assets/themes/solarized-light.json index d0c1f4e6e8..c61d354d35 100644 --- a/assets/themes/solarized-light.json +++ b/assets/themes/solarized-light.json @@ -474,6 +474,21 @@ "notification": { "margin": { "top": 10 + }, + "background": "#eee8d5", + "corner_radius": 6, + "padding": 12, + "border": { + "color": "#fdf6e3", + "width": 1 + }, + "shadow": { + "blur": 16, + "color": "#0000001f", + "offset": [ + 0, + 2 + ] } }, "notifications": { @@ -481,8 +496,7 @@ "margin": { "right": 10, "bottom": 10 - }, - "background": "#ff0000" + } } }, "editor": { @@ -1659,5 +1673,48 @@ "padding": { "left": 6 } + }, + "incoming_request_notification": { + "header_avatar": { + "height": 12, + "width": 12, + "corner_radius": 6 + }, + "header_message": { + "family": "Zed Sans", + "color": "#073642", + "size": 12, + "margin": { + "left": 4 + } + }, + "header_height": 18, + "body_message": { + "family": "Zed Sans", + "color": "#586e75", + "size": 12, + "margin": { + "top": 6, + "bottom": 6 + } + }, + "button": { + "family": "Zed Sans", + "color": "#073642", + "size": 12, + "background": "#fdf6e3", + "padding": 4, + "corner_radius": 6, + "margin": { + "left": 6 + } + }, + "dismiss_button": { + "color": "#586e75", + "icon_width": 8, + "icon_height": 8, + "button_width": 8, + "button_height": 8 + } } } \ No newline at end of file diff --git a/assets/themes/sulphurpool-dark.json b/assets/themes/sulphurpool-dark.json index 05541f91f8..907ec58cc0 100644 --- a/assets/themes/sulphurpool-dark.json +++ b/assets/themes/sulphurpool-dark.json @@ -474,6 +474,21 @@ "notification": { "margin": { "top": 10 + }, + "background": "#293256", + "corner_radius": 6, + "padding": 12, + "border": { + "color": "#202746", + "width": 1 + }, + "shadow": { + "blur": 16, + "color": "#0000003d", + "offset": [ + 0, + 2 + ] } }, "notifications": { @@ -481,8 +496,7 @@ "margin": { "right": 10, "bottom": 10 - }, - "background": "#ff0000" + } } }, "editor": { @@ -1659,5 +1673,48 @@ "padding": { "left": 6 } + }, + "incoming_request_notification": { + "header_avatar": { + "height": 12, + "width": 12, + "corner_radius": 6 + }, + "header_message": { + "family": "Zed Sans", + "color": "#dfe2f1", + "size": 12, + "margin": { + "left": 4 + } + }, + "header_height": 18, + "body_message": { + "family": "Zed Sans", + "color": "#979db4", + "size": 12, + "margin": { + "top": 6, + "bottom": 6 + } + }, + "button": { + "family": "Zed Sans", + "color": "#dfe2f1", + "size": 12, + "background": "#202746", + "padding": 4, + "corner_radius": 6, + "margin": { + "left": 6 + } + }, + "dismiss_button": { + "color": "#979db4", + "icon_width": 8, + "icon_height": 8, + "button_width": 8, + "button_height": 8 + } } } \ No newline at end of file diff --git a/assets/themes/sulphurpool-light.json b/assets/themes/sulphurpool-light.json index 1a9408b17f..3ae43250f0 100644 --- a/assets/themes/sulphurpool-light.json +++ b/assets/themes/sulphurpool-light.json @@ -474,6 +474,21 @@ "notification": { "margin": { "top": 10 + }, + "background": "#dfe2f1", + "corner_radius": 6, + "padding": 12, + "border": { + "color": "#f5f7ff", + "width": 1 + }, + "shadow": { + "blur": 16, + "color": "#0000001f", + "offset": [ + 0, + 2 + ] } }, "notifications": { @@ -481,8 +496,7 @@ "margin": { "right": 10, "bottom": 10 - }, - "background": "#ff0000" + } } }, "editor": { @@ -1659,5 +1673,48 @@ "padding": { "left": 6 } + }, + "incoming_request_notification": { + "header_avatar": { + "height": 12, + "width": 12, + "corner_radius": 6 + }, + "header_message": { + "family": "Zed Sans", + "color": "#293256", + "size": 12, + "margin": { + "left": 4 + } + }, + "header_height": 18, + "body_message": { + "family": "Zed Sans", + "color": "#5e6687", + "size": 12, + "margin": { + "top": 6, + "bottom": 6 + } + }, + "button": { + "family": "Zed Sans", + "color": "#293256", + "size": 12, + "background": "#f5f7ff", + "padding": 4, + "corner_radius": 6, + "margin": { + "left": 6 + } + }, + "dismiss_button": { + "color": "#5e6687", + "icon_width": 8, + "icon_height": 8, + "button_width": 8, + "button_height": 8 + } } } \ No newline at end of file diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index a0f0884294..4d5f44c320 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -356,6 +356,24 @@ impl UserStore { ) } + pub fn dismiss_contact_request( + &mut self, + requester_id: u64, + cx: &mut ModelContext, + ) -> Task> { + let client = self.client.upgrade(); + cx.spawn_weak(|_, _| async move { + client + .ok_or_else(|| anyhow!("can't upgrade client reference"))? + .request(proto::RespondToContactRequest { + requester_id, + response: proto::ContactRequestResponse::Dismiss as i32, + }) + .await?; + Ok(()) + }) + } + fn perform_contact_request( &mut self, user_id: u64, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 33d1d52677..0a34e75b3a 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1023,35 +1023,42 @@ impl Server { .await .user_id_for_connection(request.sender_id)?; let requester_id = UserId::from_proto(request.payload.requester_id); - let accept = request.payload.response == proto::ContactRequestResponse::Accept as i32; - self.app_state - .db - .respond_to_contact_request(responder_id, requester_id, accept) - .await?; + if request.payload.response == proto::ContactRequestResponse::Dismiss as i32 { + self.app_state + .db + .dismiss_contact_request(responder_id, requester_id) + .await?; + } else { + let accept = request.payload.response == proto::ContactRequestResponse::Accept as i32; + self.app_state + .db + .respond_to_contact_request(responder_id, requester_id, accept) + .await?; - let store = self.store().await; - // Update responder with new contact - let mut update = proto::UpdateContacts::default(); - if accept { - update.contacts.push(store.contact_for_user(requester_id)); - } - update - .remove_incoming_requests - .push(requester_id.to_proto()); - for connection_id in store.connection_ids_for_user(responder_id) { - self.peer.send(connection_id, update.clone())?; - } + let store = self.store().await; + // Update responder with new contact + let mut update = proto::UpdateContacts::default(); + if accept { + update.contacts.push(store.contact_for_user(requester_id)); + } + update + .remove_incoming_requests + .push(requester_id.to_proto()); + for connection_id in store.connection_ids_for_user(responder_id) { + self.peer.send(connection_id, update.clone())?; + } - // Update requester with new contact - let mut update = proto::UpdateContacts::default(); - if accept { - update.contacts.push(store.contact_for_user(responder_id)); - } - update - .remove_outgoing_requests - .push(responder_id.to_proto()); - for connection_id in store.connection_ids_for_user(requester_id) { - self.peer.send(connection_id, update.clone())?; + // Update requester with new contact + let mut update = proto::UpdateContacts::default(); + if accept { + update.contacts.push(store.contact_for_user(responder_id)); + } + update + .remove_outgoing_requests + .push(responder_id.to_proto()); + for connection_id in store.connection_ids_for_user(requester_id) { + self.peer.send(connection_id, update.clone())?; + } } response.send(proto::Ack {})?; diff --git a/crates/contacts_panel/src/contact_notifications.rs b/crates/contacts_panel/src/contact_notifications.rs index 894c1f4138..3b31528b71 100644 --- a/crates/contacts_panel/src/contact_notifications.rs +++ b/crates/contacts_panel/src/contact_notifications.rs @@ -1,13 +1,26 @@ use client::{User, UserStore}; -use gpui::{color::Color, elements::*, Entity, ModelHandle, View, ViewContext}; +use gpui::{ + elements::*, impl_internal_actions, platform::CursorStyle, Entity, ModelHandle, + MutableAppContext, RenderContext, View, ViewContext, +}; +use settings::Settings; use std::sync::Arc; use workspace::Notification; +impl_internal_actions!(contact_notifications, [Dismiss]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(IncomingRequestNotification::dismiss); +} + pub struct IncomingRequestNotification { user: Arc, user_store: ModelHandle, } +#[derive(Clone)] +struct Dismiss(u64); + pub enum Event { Dismiss, } @@ -21,12 +34,91 @@ impl View for IncomingRequestNotification { "IncomingRequestNotification" } - fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { - Empty::new() - .constrained() - .with_height(200.) + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + enum Dismiss {} + enum Reject {} + enum Accept {} + + let theme = cx.global::().theme.clone(); + let theme = &theme.incoming_request_notification; + let user_id = self.user.id; + + Flex::column() + .with_child( + Flex::row() + .with_children(self.user.avatar.clone().map(|avatar| { + Image::new(avatar) + .with_style(theme.header_avatar) + .aligned() + .left() + .boxed() + })) + .with_child( + Label::new( + format!("{} added you", self.user.github_login), + theme.header_message.text.clone(), + ) + .contained() + .with_style(theme.header_message.container) + .aligned() + .boxed(), + ) + .with_child( + MouseEventHandler::new::( + self.user.id as usize, + cx, + |_, _| { + Svg::new("icons/reject.svg") + .with_color(theme.dismiss_button.color) + .constrained() + .with_width(theme.dismiss_button.icon_width) + .aligned() + .contained() + .with_style(theme.dismiss_button.container) + .constrained() + .with_width(theme.dismiss_button.button_width) + .with_height(theme.dismiss_button.button_width) + .aligned() + .boxed() + }, + ) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(move |_, cx| cx.dispatch_action(Dismiss(user_id))) + .flex_float() + .boxed(), + ) + .constrained() + .with_height(theme.header_height) + .boxed(), + ) + .with_child( + Label::new( + "They won't know if you decline.".to_string(), + theme.body_message.text.clone(), + ) + .contained() + .with_style(theme.body_message.container) + .boxed(), + ) + .with_child( + Flex::row() + .with_child( + Label::new("Decline".to_string(), theme.button.text.clone()) + .contained() + .with_style(theme.button.container) + .boxed(), + ) + .with_child( + Label::new("Accept".to_string(), theme.button.text.clone()) + .contained() + .with_style(theme.button.container) + .boxed(), + ) + .aligned() + .right() + .boxed(), + ) .contained() - .with_background_color(Color::red()) .boxed() } } @@ -55,4 +147,13 @@ impl IncomingRequestNotification { Self { user, user_store } } + + fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext) { + self.user_store.update(cx, |store, cx| { + store + .dismiss_contact_request(self.user.id, cx) + .detach_and_log_err(cx); + }); + cx.emit(Event::Dismiss); + } } diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 68fb8e1f26..333de8c3d5 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -55,6 +55,7 @@ pub struct RespondToContactRequest { pub fn init(cx: &mut MutableAppContext) { contact_finder::init(cx); + contact_notifications::init(cx); cx.add_action(ContactsPanel::request_contact); cx.add_action(ContactsPanel::remove_contact); cx.add_action(ContactsPanel::respond_to_contact_request); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 8adba5fc80..c92b8c5c00 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -566,6 +566,7 @@ enum ContactRequestResponse { Accept = 0; Reject = 1; Block = 2; + Dismiss = 3; } message SendChannelMessage { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 58cdc7fc54..aeb656828e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -29,6 +29,7 @@ pub struct Theme { pub search: Search, pub project_diagnostics: ProjectDiagnostics, pub breadcrumbs: ContainedText, + pub incoming_request_notification: IncomingRequestNotification, } #[derive(Deserialize, Default)] @@ -354,6 +355,16 @@ pub struct ProjectDiagnostics { pub tab_summary_spacing: f32, } +#[derive(Deserialize, Default)] +pub struct IncomingRequestNotification { + pub header_avatar: ImageStyle, + pub header_message: ContainedText, + pub header_height: f32, + pub body_message: ContainedText, + pub button: ContainedText, + pub dismiss_button: IconButton, +} + #[derive(Clone, Deserialize, Default)] pub struct Editor { pub text_color: Color, diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index 0da6ada222..b4b9ffe383 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -10,6 +10,7 @@ import search from "./search"; import picker from "./picker"; import workspace from "./workspace"; import projectDiagnostics from "./projectDiagnostics"; +import incomingRequestNotification from "./incomingRequestNotification"; export const panel = { padding: { top: 12, left: 12, bottom: 12, right: 12 }, @@ -32,6 +33,7 @@ export default function app(theme: Theme): Object { padding: { left: 6, }, - } + }, + incomingRequestNotification: incomingRequestNotification(theme), }; } diff --git a/styles/src/styleTree/incomingRequestNotification.ts b/styles/src/styleTree/incomingRequestNotification.ts new file mode 100644 index 0000000000..17cfad80d6 --- /dev/null +++ b/styles/src/styleTree/incomingRequestNotification.ts @@ -0,0 +1,35 @@ +import Theme from "../themes/theme"; +import { backgroundColor, iconColor, text } from "./components"; + +export default function incomingRequestNotification(theme: Theme): Object { + return { + headerAvatar: { + height: 12, + width: 12, + cornerRadius: 6, + }, + headerMessage: { + ...text(theme, "sans", "primary", { size: "xs" }), + margin: { left: 4 } + }, + headerHeight: 18, + bodyMessage: { + ...text(theme, "sans", "secondary", { size: "xs" }), + margin: { top: 6, bottom: 6 }, + }, + button: { + ...text(theme, "sans", "primary", { size: "xs" }), + background: backgroundColor(theme, "on300"), + padding: 4, + cornerRadius: 6, + margin: { left: 6 }, + }, + dismissButton: { + color: iconColor(theme, "secondary"), + iconWidth: 8, + iconHeight: 8, + buttonWidth: 8, + buttonHeight: 8, + } + } +} \ No newline at end of file diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 72547627fa..1d4b78944f 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -1,5 +1,5 @@ import Theme from "../themes/theme"; -import { backgroundColor, border, iconColor, text } from "./components"; +import { backgroundColor, border, iconColor, shadow, text } from "./components"; import statusBar from "./statusBar"; export default function workspace(theme: Theme) { @@ -148,11 +148,15 @@ export default function workspace(theme: Theme) { }, notification: { margin: { top: 10 }, + background: backgroundColor(theme, 300), + cornerRadius: 6, + padding: 12, + border: border(theme, "primary"), + shadow: shadow(theme), }, notifications: { width: 256, margin: { right: 10, bottom: 10 }, - background: "#ff0000", } }; }