Detailed changes
@@ -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
+ }
}
}
@@ -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
+ }
}
}
@@ -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
+ }
}
}
@@ -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
+ }
}
}
@@ -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
+ }
}
}
@@ -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
+ }
}
}
@@ -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
+ }
}
}
@@ -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
+ }
}
}
@@ -356,6 +356,24 @@ impl UserStore {
)
}
+ pub fn dismiss_contact_request(
+ &mut self,
+ requester_id: u64,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<()>> {
+ 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<T: RequestMessage>(
&mut self,
user_id: u64,
@@ -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?;
-
- 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())?;
- }
+ 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())?;
+ }
- // 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 {})?;
@@ -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>,
user_store: ModelHandle<UserStore>,
}
+#[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<Self>) -> ElementBox {
+ enum Dismiss {}
+ enum Reject {}
+ enum Accept {}
+
+ let theme = cx.global::<Settings>().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::<Dismiss, _, _>(
+ 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>) {
+ self.user_store.update(cx, |store, cx| {
+ store
+ .dismiss_contact_request(self.user.id, cx)
+ .detach_and_log_err(cx);
+ });
+ cx.emit(Event::Dismiss);
+ }
}
@@ -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);
@@ -566,6 +566,7 @@ enum ContactRequestResponse {
Accept = 0;
Reject = 1;
Block = 2;
+ Dismiss = 3;
}
message SendChannelMessage {
@@ -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,
@@ -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),
};
}
@@ -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,
+ }
+ }
+}
@@ -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",
}
};
}