mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
skill_creator: Use the status toast for confirming creation (#57855)
This PR uses the status toast for the Skill Creator confirming action as opposed the regular message notification. Aside from it looking a bit better, it's also auto-dismissed, which is preferred in this case. Release Notes: - Improved skill creation toast confirmation by making it auto-dismissed. --------- Co-authored-by: Martin Ye <martin@zed.dev>
This commit is contained in:
parent
7ae24463e1
commit
6472f018a6
6 changed files with 93 additions and 59 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -17229,6 +17229,7 @@ dependencies = [
|
|||
"http_client",
|
||||
"language",
|
||||
"menu",
|
||||
"notifications",
|
||||
"platform_title_bar",
|
||||
"release_channel",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -1563,6 +1563,7 @@
|
|||
"context": "SkillCreator",
|
||||
"bindings": {
|
||||
"ctrl-w": "workspace::CloseWindow",
|
||||
"ctrl-enter": "skill_creator::SaveSkill",
|
||||
"tab": "skill_creator::FocusNextField",
|
||||
"shift-tab": "skill_creator::FocusPreviousField",
|
||||
},
|
||||
|
|
@ -1571,6 +1572,7 @@
|
|||
"context": "SkillCreator > Editor",
|
||||
"bindings": {
|
||||
"ctrl-w": "workspace::CloseWindow",
|
||||
"ctrl-enter": "skill_creator::SaveSkill",
|
||||
"tab": "skill_creator::FocusNextField",
|
||||
"shift-tab": "skill_creator::FocusPreviousField",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1657,6 +1657,7 @@
|
|||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-w": "workspace::CloseWindow",
|
||||
"cmd-enter": "skill_creator::SaveSkill",
|
||||
"tab": "skill_creator::FocusNextField",
|
||||
"shift-tab": "skill_creator::FocusPreviousField",
|
||||
},
|
||||
|
|
@ -1666,6 +1667,7 @@
|
|||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-w": "workspace::CloseWindow",
|
||||
"cmd-enter": "skill_creator::SaveSkill",
|
||||
"tab": "skill_creator::FocusNextField",
|
||||
"shift-tab": "skill_creator::FocusPreviousField",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1583,6 +1583,7 @@
|
|||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-w": "workspace::CloseWindow",
|
||||
"ctrl-enter": "skill_creator::SaveSkill",
|
||||
"tab": "skill_creator::FocusNextField",
|
||||
"shift-tab": "skill_creator::FocusPreviousField",
|
||||
},
|
||||
|
|
@ -1592,6 +1593,7 @@
|
|||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-w": "workspace::CloseWindow",
|
||||
"ctrl-enter": "skill_creator::SaveSkill",
|
||||
"tab": "skill_creator::FocusNextField",
|
||||
"shift-tab": "skill_creator::FocusPreviousField",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ gpui.workspace = true
|
|||
http_client.workspace = true
|
||||
language.workspace = true
|
||||
menu.workspace = true
|
||||
notifications.workspace = true
|
||||
platform_title_bar.workspace = true
|
||||
release_channel.workspace = true
|
||||
serde_yaml_ng.workspace = true
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use gpui::{
|
|||
};
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request, StatusCode, Url};
|
||||
use language::{Buffer, LanguageRegistry, language_settings::SoftWrap};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use platform_title_bar::PlatformTitleBar;
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::{ActionSequence, Settings};
|
||||
|
|
@ -22,14 +23,12 @@ use std::sync::Arc;
|
|||
use std::time::Duration;
|
||||
use theme_settings::ThemeSettings;
|
||||
use ui::{
|
||||
ContextMenu, Divider, DropdownMenu, DropdownStyle, Headline, HeadlineSize, SwitchField,
|
||||
Banner, ContextMenu, Divider, DropdownMenu, DropdownStyle, Headline, HeadlineSize, SwitchField,
|
||||
WithScrollbar, prelude::*,
|
||||
};
|
||||
use ui_input::{ErasedEditorEvent, InputField};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
Toast, Workspace, WorkspaceSettings, client_side_decorations, notifications::NotificationId,
|
||||
};
|
||||
use workspace::{Workspace, WorkspaceSettings, client_side_decorations};
|
||||
use worktree::WorktreeId;
|
||||
|
||||
actions!(
|
||||
|
|
@ -43,6 +42,8 @@ const DESCRIPTION_FIELD_TAB_INDEX: isize = 3;
|
|||
const DISABLE_MODEL_INVOCATION_TAB_INDEX: isize = 4;
|
||||
const SCOPE_FIELD_TAB_INDEX: isize = 5;
|
||||
const BODY_FIELD_TAB_INDEX: isize = 6;
|
||||
const CANCEL_BUTTON_TAB_INDEX: isize = 7;
|
||||
const SAVE_BUTTON_TAB_INDEX: isize = 8;
|
||||
const URL_IMPORT_DEBOUNCE: Duration = Duration::from_millis(100);
|
||||
const URL_IMPORT_ERROR_BODY_MAX_LEN: usize = 2048;
|
||||
|
||||
|
|
@ -83,13 +84,6 @@ enum ScopeChoice {
|
|||
}
|
||||
|
||||
impl ScopeChoice {
|
||||
fn label(&self) -> SharedString {
|
||||
match self {
|
||||
ScopeChoice::Global => "Global".into(),
|
||||
ScopeChoice::Project { root_name, .. } => root_name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn key(&self) -> SharedString {
|
||||
match self {
|
||||
ScopeChoice::Global => "global".into(),
|
||||
|
|
@ -256,6 +250,8 @@ pub struct SkillCreator {
|
|||
// Held so replacing it or switching back to the form cancels an in-flight import.
|
||||
url_import_task: Option<Task<()>>,
|
||||
scroll_handle: ScrollHandle,
|
||||
cancel_button_focus_handle: FocusHandle,
|
||||
save_button_focus_handle: FocusHandle,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
|
|
@ -432,6 +428,8 @@ impl SkillCreator {
|
|||
url_import_debounce_task: None,
|
||||
url_import_task: None,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
cancel_button_focus_handle: cx.focus_handle(),
|
||||
save_button_focus_handle: cx.focus_handle(),
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
|
@ -728,7 +726,10 @@ impl SkillCreator {
|
|||
let disable_model_invocation = self.disable_model_invocation;
|
||||
let fs = self.fs.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let scope_label = scope.label();
|
||||
let scope_description: SharedString = match &scope {
|
||||
ScopeChoice::Global => "your global skills".into(),
|
||||
ScopeChoice::Project { root_name, .. } => root_name.clone(),
|
||||
};
|
||||
|
||||
self.saving = true;
|
||||
self.save_error = None;
|
||||
|
|
@ -749,22 +750,23 @@ impl SkillCreator {
|
|||
this.saving = false;
|
||||
this.save_task = None;
|
||||
match result {
|
||||
Ok(path) => {
|
||||
Ok(_) => {
|
||||
if let Some(on_saved) = &this.on_saved {
|
||||
on_saved(cx);
|
||||
}
|
||||
if let Some(workspace) = workspace.as_ref().and_then(|w| w.upgrade()) {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.show_toast(
|
||||
Toast::new(
|
||||
NotificationId::unique::<SaveSkill>(),
|
||||
format!(
|
||||
"Saved skill \"{name}\" to {scope_label} ({})",
|
||||
path.display()
|
||||
),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
let message =
|
||||
format!("Saved skill \"{name}\" to {scope_description}");
|
||||
let status_toast = StatusToast::new(message, cx, |this, _cx| {
|
||||
this.icon(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
});
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
});
|
||||
}
|
||||
window.remove_window();
|
||||
|
|
@ -1016,45 +1018,77 @@ impl SkillCreator {
|
|||
))
|
||||
}
|
||||
|
||||
fn render_action_bar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render_footer(&self, window: &Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let valid = self.is_valid(cx);
|
||||
let saving = self.saving;
|
||||
let main_action = if saving { "Saving…" } else { "Save Skill" };
|
||||
|
||||
h_flex()
|
||||
// Draw a faint outline around whichever button currently holds
|
||||
// keyboard focus, so tabbing to Cancel/Save is clearly visible. The
|
||||
// ring border is always present (transparent when unfocused) so
|
||||
// focusing a button never shifts the surrounding layout.
|
||||
let focus_ring = |focus_handle: &FocusHandle| {
|
||||
let focused = focus_handle.is_focused(window) && window.last_input_was_keyboard();
|
||||
let border_color = if focused {
|
||||
cx.theme().colors().border_focused
|
||||
} else {
|
||||
cx.theme().colors().border_transparent
|
||||
};
|
||||
div().rounded_sm().border_1().border_color(border_color)
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.map(|this| {
|
||||
if self.save_error.is_some() {
|
||||
this.justify_between()
|
||||
} else {
|
||||
this.justify_end()
|
||||
}
|
||||
.p_2p5()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.when(self.save_error.is_some(), |this| {
|
||||
this.gap_2().child(
|
||||
Banner::new()
|
||||
.severity(Severity::Error)
|
||||
.children(self.save_error.clone().map(|err| Label::new(err))),
|
||||
)
|
||||
})
|
||||
.gap_2()
|
||||
.children(
|
||||
self.save_error
|
||||
.clone()
|
||||
.map(|err| Label::new(err).size(LabelSize::Small).color(Color::Error)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.child(
|
||||
Button::new("cancel-skill", "Cancel")
|
||||
.disabled(saving)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(Cancel), cx);
|
||||
}),
|
||||
focus_ring(&self.cancel_button_focus_handle).child(
|
||||
Button::new("cancel-skill", "Cancel")
|
||||
.track_focus(
|
||||
&self
|
||||
.cancel_button_focus_handle
|
||||
.clone()
|
||||
.tab_index(CANCEL_BUTTON_TAB_INDEX)
|
||||
.tab_stop(true),
|
||||
)
|
||||
.disabled(saving)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(Cancel), cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new("save-skill", main_action)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ui::ElevationIndex::ModalSurface)
|
||||
.disabled(!valid || saving)
|
||||
.loading(saving)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(SaveSkill), cx);
|
||||
}),
|
||||
focus_ring(&self.save_button_focus_handle).child(
|
||||
Button::new("save-skill", main_action)
|
||||
.track_focus(
|
||||
&self
|
||||
.save_button_focus_handle
|
||||
.clone()
|
||||
.tab_index(SAVE_BUTTON_TAB_INDEX)
|
||||
.tab_stop(true),
|
||||
)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ui::ElevationIndex::ModalSurface)
|
||||
.disabled(!valid || saving)
|
||||
.loading(saving)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(SaveSkill), cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1165,15 +1199,7 @@ impl Render for SkillCreator {
|
|||
.child(self.render_form_fields(window, cx)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.p_2p5()
|
||||
.border_t_1()
|
||||
.border_color(theme.colors().border_variant)
|
||||
.bg(theme.colors().panel_background)
|
||||
.child(self.render_action_bar(cx)),
|
||||
),
|
||||
.child(self.render_footer(window, cx)),
|
||||
window,
|
||||
cx,
|
||||
Tiling::default(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue