Rework git toasts (#26420)
The notifications from git output could take up variable amounts of screen space, and they were quite obnoxious when a git command printed lots of output, such as fetching many new branches or verbose push hooks. This change makes the push/pull/fetch buttons trigger a small notification toast, based on the output of the command that was ran. For errors or commands with more output the user may want to see, there's an "Open Log" button which opens a new buffer with the output of that command. It also uses this behavior for long error notifications for other git commands like `commit` and `checkout`. The output of those commands can be quite long due to arbitrary githooks running. Release Notes: - N/A --------- Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
parent
e8208643bb
commit
2b94a35aaa
14 changed files with 415 additions and 418 deletions
|
@ -1,12 +1,70 @@
|
|||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
rc::Rc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use gpui::{AnyView, DismissEvent, Entity, FocusHandle, ManagedView, Subscription, Task};
|
||||
use gpui::{actions, AnyView, DismissEvent, Entity, FocusHandle, ManagedView, Subscription, Task};
|
||||
use ui::{animation::DefaultAnimations, prelude::*};
|
||||
|
||||
use crate::Workspace;
|
||||
|
||||
const DEFAULT_TOAST_DURATION: Duration = Duration::from_millis(2400);
|
||||
const MINIMUM_RESUME_DURATION: Duration = Duration::from_millis(800);
|
||||
|
||||
pub trait ToastView: ManagedView {}
|
||||
actions!(toast, [RunAction]);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
|
||||
workspace.register_action(|_workspace, _: &RunAction, window, cx| {
|
||||
let workspace = cx.entity();
|
||||
let window = window.window_handle();
|
||||
cx.defer(move |cx| {
|
||||
let action = workspace
|
||||
.read(cx)
|
||||
.toast_layer
|
||||
.read(cx)
|
||||
.active_toast
|
||||
.as_ref()
|
||||
.and_then(|active_toast| active_toast.action.clone());
|
||||
|
||||
if let Some(on_click) = action.and_then(|action| action.on_click) {
|
||||
window
|
||||
.update(cx, |_, window, cx| {
|
||||
on_click(window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub trait ToastView: ManagedView {
|
||||
fn action(&self) -> Option<ToastAction>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToastAction {
|
||||
pub id: ElementId,
|
||||
pub label: SharedString,
|
||||
pub on_click: Option<Rc<dyn Fn(&mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
||||
impl ToastAction {
|
||||
pub fn new(
|
||||
label: SharedString,
|
||||
on_click: Option<Rc<dyn Fn(&mut Window, &mut App) + 'static>>,
|
||||
) -> Self {
|
||||
let id = ElementId::Name(label.clone());
|
||||
|
||||
Self {
|
||||
id,
|
||||
label,
|
||||
on_click,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ToastViewHandle {
|
||||
fn view(&self) -> AnyView;
|
||||
|
@ -20,6 +78,7 @@ impl<V: ToastView> ToastViewHandle for Entity<V> {
|
|||
|
||||
pub struct ActiveToast {
|
||||
toast: Box<dyn ToastViewHandle>,
|
||||
action: Option<ToastAction>,
|
||||
_subscriptions: [Subscription; 1],
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
@ -50,52 +109,43 @@ impl ToastLayer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn toggle_toast<V>(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
new_toast: Entity<V>,
|
||||
) where
|
||||
pub fn toggle_toast<V>(&mut self, cx: &mut Context<Self>, new_toast: Entity<V>)
|
||||
where
|
||||
V: ToastView,
|
||||
{
|
||||
if let Some(active_toast) = &self.active_toast {
|
||||
let is_close = active_toast.toast.view().downcast::<V>().is_ok();
|
||||
let did_close = self.hide_toast(window, cx);
|
||||
let did_close = self.hide_toast(cx);
|
||||
if is_close || !did_close {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.show_toast(new_toast, window, cx);
|
||||
self.show_toast(new_toast, cx);
|
||||
}
|
||||
|
||||
pub fn show_toast<V>(
|
||||
&mut self,
|
||||
new_toast: Entity<V>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) where
|
||||
pub fn show_toast<V>(&mut self, new_toast: Entity<V>, cx: &mut Context<Self>)
|
||||
where
|
||||
V: ToastView,
|
||||
{
|
||||
let action = new_toast.read(cx).action();
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
self.active_toast = Some(ActiveToast {
|
||||
toast: Box::new(new_toast.clone()),
|
||||
_subscriptions: [cx.subscribe_in(
|
||||
&new_toast,
|
||||
window,
|
||||
|this, _, _: &DismissEvent, window, cx| {
|
||||
this.hide_toast(window, cx);
|
||||
},
|
||||
)],
|
||||
action,
|
||||
_subscriptions: [cx.subscribe(&new_toast, |this, _, _: &DismissEvent, cx| {
|
||||
this.hide_toast(cx);
|
||||
})],
|
||||
focus_handle,
|
||||
});
|
||||
|
||||
self.start_dismiss_timer(DEFAULT_TOAST_DURATION, window, cx);
|
||||
self.start_dismiss_timer(DEFAULT_TOAST_DURATION, cx);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn hide_toast(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> bool {
|
||||
pub fn hide_toast(&mut self, cx: &mut Context<Self>) -> bool {
|
||||
self.active_toast.take();
|
||||
cx.notify();
|
||||
|
||||
true
|
||||
|
@ -128,12 +178,7 @@ impl ToastLayer {
|
|||
}
|
||||
|
||||
/// Starts a timer to automatically dismiss the toast after the specified duration
|
||||
pub fn start_dismiss_timer(
|
||||
&mut self,
|
||||
duration: Duration,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn start_dismiss_timer(&mut self, duration: Duration, cx: &mut Context<Self>) {
|
||||
self.clear_dismiss_timer(cx);
|
||||
|
||||
let instant_started = std::time::Instant::now();
|
||||
|
@ -141,11 +186,7 @@ impl ToastLayer {
|
|||
cx.background_executor().timer(duration).await;
|
||||
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.active_toast.take();
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
this.update(&mut cx, |this, cx| this.hide_toast(cx)).ok();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -158,11 +199,11 @@ impl ToastLayer {
|
|||
}
|
||||
|
||||
/// Restarts the dismiss timer with a new duration
|
||||
pub fn restart_dismiss_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub fn restart_dismiss_timer(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(duration) = self.duration_remaining else {
|
||||
return;
|
||||
};
|
||||
self.start_dismiss_timer(duration, window, cx);
|
||||
self.start_dismiss_timer(duration, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -194,14 +235,14 @@ impl Render for ToastLayer {
|
|||
h_flex()
|
||||
.id("active-toast-container")
|
||||
.occlude()
|
||||
.on_hover(move |hover_start, window, cx| {
|
||||
.on_hover(move |hover_start, _window, cx| {
|
||||
let Some(this) = handle.upgrade() else {
|
||||
return;
|
||||
};
|
||||
if *hover_start {
|
||||
this.update(cx, |this, _| this.pause_dismiss_timer());
|
||||
} else {
|
||||
this.update(cx, |this, cx| this.restart_dismiss_timer(window, cx));
|
||||
this.update(cx, |this, cx| this.restart_dismiss_timer(cx));
|
||||
}
|
||||
cx.stop_propagation();
|
||||
})
|
||||
|
|
|
@ -14,7 +14,7 @@ mod toast_layer;
|
|||
mod toolbar;
|
||||
mod workspace_settings;
|
||||
|
||||
pub use toast_layer::{ToastLayer, ToastView};
|
||||
pub use toast_layer::{RunAction, ToastAction, ToastLayer, ToastView};
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use call::{call_settings::CallSettings, ActiveCall};
|
||||
|
@ -384,6 +384,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) {
|
|||
init_settings(cx);
|
||||
component::init();
|
||||
theme_preview::init(cx);
|
||||
toast_layer::init(cx);
|
||||
|
||||
cx.on_action(Workspace::close_global);
|
||||
cx.on_action(reload);
|
||||
|
@ -5016,15 +5017,9 @@ impl Workspace {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn toggle_status_toast<V: ToastView>(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
entity: Entity<V>,
|
||||
) {
|
||||
self.toast_layer.update(cx, |toast_layer, cx| {
|
||||
toast_layer.toggle_toast(window, cx, entity)
|
||||
})
|
||||
pub fn toggle_status_toast<V: ToastView>(&mut self, entity: Entity<V>, cx: &mut App) {
|
||||
self.toast_layer
|
||||
.update(cx, |toast_layer, cx| toast_layer.toggle_toast(cx, entity))
|
||||
}
|
||||
|
||||
pub fn toggle_centered_layout(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue