Adds a way to dismiss workspace notifications (#30015)
Closes https://github.com/zed-industries/zed/issues/10140 * On `menu::Cancel` action (`ESC`), close notifications, one by one, if `Workspace` gets to handle this action. More specific, focused items contexts (e.g. `Editor`) take priority. * Allows to temporarily suppress notifications of this kind either by clicking a corresponding button in the UI, or using `workspace::SuppressNotification` action. This might not work well out of the box for all notifications and might require further improvement. https://github.com/user-attachments/assets/0ea49ee6-cd21-464f-ba74-fc40f7a8dedf Release Notes: - Added a way to dismiss workspace notifications
This commit is contained in:
parent
7d361ec97e
commit
007fd0586a
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -18158,6 +18158,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
|
@ -22,7 +22,9 @@ use ui::{
|
||||
Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use workspace::notifications::{Notification as WorkspaceNotification, NotificationId};
|
||||
use workspace::notifications::{
|
||||
Notification as WorkspaceNotification, NotificationId, SuppressEvent,
|
||||
};
|
||||
use workspace::{
|
||||
Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
@ -823,6 +825,11 @@ impl Render for NotificationToast {
|
||||
IconButton::new("close", IconName::Close)
|
||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("suppress", IconName::XCircle)
|
||||
.tooltip(Tooltip::text("Do not show until restart"))
|
||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(SuppressEvent))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.focus_notification_panel(window, cx);
|
||||
cx.emit(DismissEvent);
|
||||
@ -831,3 +838,4 @@ impl Render for NotificationToast {
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for NotificationToast {}
|
||||
impl EventEmitter<SuppressEvent> for NotificationToast {}
|
||||
|
@ -43,6 +43,7 @@ http_client.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
node_runtime.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
|
@ -29,7 +29,7 @@ impl std::ops::DerefMut for Notifications {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub enum NotificationId {
|
||||
Unique(TypeId),
|
||||
Composite(TypeId, ElementId),
|
||||
@ -54,7 +54,12 @@ impl NotificationId {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Notification: EventEmitter<DismissEvent> + Focusable + Render {}
|
||||
pub trait Notification:
|
||||
EventEmitter<DismissEvent> + EventEmitter<SuppressEvent> + Focusable + Render
|
||||
{
|
||||
}
|
||||
|
||||
pub struct SuppressEvent;
|
||||
|
||||
impl Workspace {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@ -81,6 +86,13 @@ impl Workspace {
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
cx.subscribe(¬ification, {
|
||||
let id = id.clone();
|
||||
move |workspace: &mut Workspace, _, _: &SuppressEvent, cx| {
|
||||
workspace.suppress_notification(&id, cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
notification.into()
|
||||
});
|
||||
}
|
||||
@ -96,6 +108,9 @@ impl Workspace {
|
||||
cx: &mut Context<Self>,
|
||||
build_notification: impl FnOnce(&mut Context<Self>) -> AnyView,
|
||||
) {
|
||||
if self.suppressed_notifications.contains(id) {
|
||||
return;
|
||||
}
|
||||
self.dismiss_notification(id, cx);
|
||||
self.notifications
|
||||
.push((id.clone(), build_notification(cx)));
|
||||
@ -172,6 +187,11 @@ impl Workspace {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn suppress_notification(&mut self, id: &NotificationId, cx: &mut Context<Self>) {
|
||||
self.dismiss_notification(id, cx);
|
||||
self.suppressed_notifications.insert(id.clone());
|
||||
}
|
||||
|
||||
pub fn show_initial_notifications(&mut self, cx: &mut Context<Self>) {
|
||||
// Allow absence of the global so that tests don't need to initialize it.
|
||||
let app_notifications = GLOBAL_APP_NOTIFICATIONS
|
||||
@ -268,6 +288,14 @@ impl Render for LanguageServerPrompt {
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
IconButton::new("suppress", IconName::XCircle)
|
||||
.tooltip(Tooltip::text("Do not show until restart"))
|
||||
.on_click(
|
||||
cx.listener(|_, _, _, cx| cx.emit(SuppressEvent)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("copy", IconName::Copy)
|
||||
.on_click({
|
||||
@ -305,6 +333,7 @@ impl Render for LanguageServerPrompt {
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for LanguageServerPrompt {}
|
||||
impl EventEmitter<SuppressEvent> for LanguageServerPrompt {}
|
||||
|
||||
fn workspace_error_notification_id() -> NotificationId {
|
||||
struct WorkspaceErrorNotification;
|
||||
@ -401,6 +430,7 @@ impl Focusable for ErrorMessagePrompt {
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for ErrorMessagePrompt {}
|
||||
impl EventEmitter<SuppressEvent> for ErrorMessagePrompt {}
|
||||
|
||||
impl Notification for ErrorMessagePrompt {}
|
||||
|
||||
@ -411,9 +441,9 @@ pub mod simple_message_notification {
|
||||
AnyElement, DismissEvent, EventEmitter, FocusHandle, Focusable, ParentElement, Render,
|
||||
SharedString, Styled, div,
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use ui::{Tooltip, prelude::*};
|
||||
|
||||
use super::Notification;
|
||||
use super::{Notification, SuppressEvent};
|
||||
|
||||
pub struct MessageNotification {
|
||||
focus_handle: FocusHandle,
|
||||
@ -429,6 +459,7 @@ pub mod simple_message_notification {
|
||||
more_info_message: Option<SharedString>,
|
||||
more_info_url: Option<Arc<str>>,
|
||||
show_close_button: bool,
|
||||
show_suppress_button: bool,
|
||||
title: Option<SharedString>,
|
||||
}
|
||||
|
||||
@ -439,6 +470,7 @@ pub mod simple_message_notification {
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for MessageNotification {}
|
||||
impl EventEmitter<SuppressEvent> for MessageNotification {}
|
||||
|
||||
impl Notification for MessageNotification {}
|
||||
|
||||
@ -470,6 +502,7 @@ pub mod simple_message_notification {
|
||||
more_info_message: None,
|
||||
more_info_url: None,
|
||||
show_close_button: true,
|
||||
show_suppress_button: true,
|
||||
title: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
@ -568,6 +601,11 @@ pub mod simple_message_notification {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_suppress_button(mut self, show: bool) -> Self {
|
||||
self.show_suppress_button = show;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_title<S>(mut self, title: S) -> Self
|
||||
where
|
||||
S: Into<SharedString>,
|
||||
@ -597,12 +635,26 @@ pub mod simple_message_notification {
|
||||
})
|
||||
.child(div().max_w_96().child((self.build_content)(window, cx))),
|
||||
)
|
||||
.when(self.show_close_button, |this| {
|
||||
this.child(
|
||||
IconButton::new("close", IconName::Close)
|
||||
.on_click(cx.listener(|this, _, _, cx| this.dismiss(cx))),
|
||||
)
|
||||
}),
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.when(self.show_suppress_button, |this| {
|
||||
this.child(
|
||||
IconButton::new("suppress", IconName::XCircle)
|
||||
.tooltip(Tooltip::text("Do not show until restart"))
|
||||
.on_click(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(SuppressEvent);
|
||||
})),
|
||||
)
|
||||
})
|
||||
.when(self.show_close_button, |this| {
|
||||
this.child(
|
||||
IconButton::new("close", IconName::Close).on_click(
|
||||
cx.listener(|this, _, _, cx| this.dismiss(cx)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
|
@ -52,7 +52,8 @@ use language::{Buffer, LanguageRegistry, Rope};
|
||||
pub use modal_layer::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
use notifications::{
|
||||
DetachAndPromptErr, Notifications, simple_message_notification::MessageNotification,
|
||||
DetachAndPromptErr, Notifications, dismiss_app_notification,
|
||||
simple_message_notification::MessageNotification,
|
||||
};
|
||||
pub use pane::*;
|
||||
pub use pane_group::*;
|
||||
@ -179,6 +180,7 @@ actions!(
|
||||
SaveAs,
|
||||
SaveWithoutFormat,
|
||||
ShutdownDebugAdapters,
|
||||
SuppressNotification,
|
||||
ToggleBottomDock,
|
||||
ToggleCenteredLayout,
|
||||
ToggleLeftDock,
|
||||
@ -921,6 +923,7 @@ pub struct Workspace {
|
||||
toast_layer: Entity<ToastLayer>,
|
||||
titlebar_item: Option<AnyView>,
|
||||
notifications: Notifications,
|
||||
suppressed_notifications: HashSet<NotificationId>,
|
||||
project: Entity<Project>,
|
||||
follower_states: HashMap<CollaboratorId, FollowerState>,
|
||||
last_leaders_by_pane: HashMap<WeakEntity<Pane>, CollaboratorId>,
|
||||
@ -1245,7 +1248,8 @@ impl Workspace {
|
||||
modal_layer,
|
||||
toast_layer,
|
||||
titlebar_item: None,
|
||||
notifications: Default::default(),
|
||||
notifications: Notifications::default(),
|
||||
suppressed_notifications: HashSet::default(),
|
||||
left_dock,
|
||||
bottom_dock,
|
||||
bottom_dock_layout,
|
||||
@ -5301,12 +5305,20 @@ impl Workspace {
|
||||
workspace.clear_all_notifications(cx);
|
||||
},
|
||||
))
|
||||
.on_action(cx.listener(
|
||||
|workspace: &mut Workspace, _: &SuppressNotification, _, cx| {
|
||||
if let Some((notification_id, _)) = workspace.notifications.pop() {
|
||||
workspace.suppress_notification(¬ification_id, cx);
|
||||
}
|
||||
},
|
||||
))
|
||||
.on_action(cx.listener(
|
||||
|workspace: &mut Workspace, _: &ReopenClosedItem, window, cx| {
|
||||
workspace.reopen_closed_item(window, cx).detach();
|
||||
},
|
||||
))
|
||||
.on_action(cx.listener(Workspace::toggle_centered_layout))
|
||||
.on_action(cx.listener(Workspace::cancel))
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@ -5477,6 +5489,15 @@ impl Workspace {
|
||||
.update(cx, |_, window, _| window.activate_window())
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some((notification_id, _)) = self.notifications.pop() {
|
||||
dismiss_app_notification(¬ification_id, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
fn leader_border_for_pane(
|
||||
|
Loading…
Reference in New Issue
Block a user