Simplify parallel agents onboarding (#53854)
- Adds a status toast to the announcement banner for surfacing the layout revert option - Removes the agent panel banner A good chunk of the diff here was because I touched up the status toast component API a little bit. Release Notes: - N/A
This commit is contained in:
1 parent
193dcb93d1
commit
034a61fe60
20 files changed
+409
-410
No files matched your search
Generated
+1
@@ -1251,6 +1251,7 @@ dependencies = [
|
||||
"fs",
|
||||
"gpui",
|
||||
"markdown_preview",
|
||||
"notifications",
|
||||
"release_channel",
|
||||
"semver",
|
||||
"serde",
|
||||
|
||||
@@ -26,7 +26,7 @@ use language_model::{
|
||||
ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use language_models::AllLanguageModelSettings;
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use project::{
|
||||
agent_server_store::{AgentId, AgentServerStore, ExternalAgentSource},
|
||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||
@@ -1330,40 +1330,44 @@ fn show_unable_to_uninstall_extension_with_context_server(
|
||||
move |this, _cx| {
|
||||
let workspace_handle = workspace_handle.clone();
|
||||
|
||||
this.icon(ToastIcon::new(IconName::Warning).color(Color::Warning))
|
||||
.dismiss_button(true)
|
||||
.action("Uninstall", move |_, _cx| {
|
||||
if let Some((extension_id, _)) =
|
||||
resolve_extension_for_context_server(&context_server_id, _cx)
|
||||
{
|
||||
ExtensionStore::global(_cx).update(_cx, |store, cx| {
|
||||
store
|
||||
.uninstall_extension(extension_id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
this.icon(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Warning),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
.action("Uninstall", move |_, _cx| {
|
||||
if let Some((extension_id, _)) =
|
||||
resolve_extension_for_context_server(&context_server_id, _cx)
|
||||
{
|
||||
ExtensionStore::global(_cx).update(_cx, |store, cx| {
|
||||
store
|
||||
.uninstall_extension(extension_id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
|
||||
workspace_handle
|
||||
.update(_cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
cx.spawn({
|
||||
let context_server_id = context_server_id.clone();
|
||||
async move |_workspace_handle, cx| {
|
||||
cx.update(|cx| {
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.project
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
});
|
||||
workspace_handle
|
||||
.update(_cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
cx.spawn({
|
||||
let context_server_id = context_server_id.clone();
|
||||
async move |_workspace_handle, cx| {
|
||||
cx.update(|cx| {
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.project
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
});
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use parking_lot::Mutex;
|
||||
use project::{
|
||||
context_server_store::{
|
||||
@@ -631,8 +631,12 @@ impl ConfigureContextServerModal {
|
||||
format!("{} configured successfully.", id.0),
|
||||
cx,
|
||||
|this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::ToolHammer).color(Color::Muted))
|
||||
.action("Dismiss", |_, _| {})
|
||||
this.icon(
|
||||
Icon::new(IconName::ToolHammer)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.action("Dismiss", |_, _| {})
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ use crate::{
|
||||
use crate::{ExpandMessageEditor, ThreadHistoryView};
|
||||
use crate::{ManageProfiles, ThreadHistoryViewEvent};
|
||||
use crate::{ThreadHistory, agent_connection_store::AgentConnectionStore};
|
||||
use agent_settings::{AgentSettings, WindowLayout};
|
||||
use agent_settings::AgentSettings;
|
||||
use ai_onboarding::AgentPanelOnboarding;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use client::UserStore;
|
||||
@@ -760,8 +760,6 @@ pub struct AgentPanel {
|
||||
pending_serialization: Option<Task<Result<()>>>,
|
||||
new_user_onboarding: Entity<AgentPanelOnboarding>,
|
||||
new_user_onboarding_upsell_dismissed: AtomicBool,
|
||||
agent_layout_onboarding: Entity<ai_onboarding::AgentLayoutOnboarding>,
|
||||
agent_layout_onboarding_dismissed: AtomicBool,
|
||||
selected_agent: Agent,
|
||||
pending_thread_loads: usize,
|
||||
worktree_creation_status: Option<(EntityId, WorktreeCreationStatus)>,
|
||||
@@ -1065,46 +1063,6 @@ impl AgentPanel {
|
||||
)
|
||||
});
|
||||
|
||||
let weak_panel = cx.entity().downgrade();
|
||||
|
||||
let layout = AgentSettings::get_layout(cx);
|
||||
let is_agent_layout = matches!(layout, WindowLayout::Agent(_));
|
||||
|
||||
let agent_layout_onboarding = cx.new(|_cx| ai_onboarding::AgentLayoutOnboarding {
|
||||
use_agent_layout: Arc::new({
|
||||
let fs = fs.clone();
|
||||
let weak_panel = weak_panel.clone();
|
||||
move |_window, cx| {
|
||||
let _ = AgentSettings::set_layout(WindowLayout::Agent(None), fs.clone(), cx);
|
||||
weak_panel
|
||||
.update(cx, |panel, cx| {
|
||||
panel.dismiss_agent_layout_onboarding(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
revert_to_editor_layout: Arc::new({
|
||||
let fs = fs.clone();
|
||||
let weak_panel = weak_panel.clone();
|
||||
move |_window, cx| {
|
||||
let _ = AgentSettings::set_layout(WindowLayout::Editor(None), fs.clone(), cx);
|
||||
weak_panel
|
||||
.update(cx, |panel, cx| {
|
||||
panel.dismiss_agent_layout_onboarding(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
dismissed: Arc::new(move |_window, cx| {
|
||||
weak_panel
|
||||
.update(cx, |panel, cx| {
|
||||
panel.dismiss_agent_layout_onboarding(cx);
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
is_agent_layout,
|
||||
});
|
||||
|
||||
// Subscribe to extension events to sync agent servers when extensions change
|
||||
let extension_subscription = if let Some(extension_events) = ExtensionEvents::try_global(cx)
|
||||
{
|
||||
@@ -1169,7 +1127,6 @@ impl AgentPanel {
|
||||
zoomed: false,
|
||||
pending_serialization: None,
|
||||
new_user_onboarding: onboarding,
|
||||
agent_layout_onboarding,
|
||||
thread_store,
|
||||
selected_agent: Agent::default(),
|
||||
pending_thread_loads: 0,
|
||||
@@ -1179,9 +1136,6 @@ impl AgentPanel {
|
||||
_worktree_creation_task: None,
|
||||
show_trust_workspace_message: false,
|
||||
new_user_onboarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed(cx)),
|
||||
agent_layout_onboarding_dismissed: AtomicBool::new(AgentLayoutOnboarding::dismissed(
|
||||
cx,
|
||||
)),
|
||||
_base_view_observation: None,
|
||||
_draft_editor_observation: None,
|
||||
};
|
||||
@@ -4676,56 +4630,10 @@ impl AgentPanel {
|
||||
plan.is_some_and(|plan| plan == Plan::ZedFree) && has_previous_trial
|
||||
}
|
||||
|
||||
fn should_render_agent_layout_onboarding(&self, cx: &mut Context<Self>) -> bool {
|
||||
// We only want to show this for existing users: those who
|
||||
// have used the agent panel before the sidebar was introduced.
|
||||
// We can infer that state by users having seen the onboarding
|
||||
// at one point, but not the agent layout onboarding.
|
||||
|
||||
let has_messages = self.active_thread_has_messages(cx);
|
||||
let is_dismissed = self
|
||||
.agent_layout_onboarding_dismissed
|
||||
.load(Ordering::Acquire);
|
||||
|
||||
if is_dismissed || has_messages {
|
||||
return false;
|
||||
}
|
||||
|
||||
match &self.base_view {
|
||||
BaseView::Uninitialized => false,
|
||||
BaseView::AgentThread { .. } => {
|
||||
let existing_user = self
|
||||
.new_user_onboarding_upsell_dismissed
|
||||
.load(Ordering::Acquire);
|
||||
existing_user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_agent_layout_onboarding(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
if !self.should_render_agent_layout_onboarding(cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(div().child(self.agent_layout_onboarding.clone()))
|
||||
}
|
||||
|
||||
fn dismiss_agent_layout_onboarding(&mut self, cx: &mut Context<Self>) {
|
||||
self.agent_layout_onboarding_dismissed
|
||||
.store(true, Ordering::Release);
|
||||
AgentLayoutOnboarding::set_dismissed(true, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn dismiss_ai_onboarding(&mut self, cx: &mut Context<Self>) {
|
||||
self.new_user_onboarding_upsell_dismissed
|
||||
.store(true, Ordering::Release);
|
||||
OnboardingUpsell::set_dismissed(true, cx);
|
||||
self.dismiss_agent_layout_onboarding(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -4987,7 +4895,6 @@ impl Render for AgentPanel {
|
||||
.child(self.render_toolbar(window, cx))
|
||||
.children(self.render_workspace_trust_message(cx))
|
||||
.children(self.render_new_user_onboarding(window, cx))
|
||||
.children(self.render_agent_layout_onboarding(window, cx))
|
||||
.map(|parent| match self.visible_surface() {
|
||||
VisibleSurface::Uninitialized => parent,
|
||||
VisibleSurface::AgentThread(conversation_view) => parent
|
||||
@@ -5078,12 +4985,6 @@ impl Dismissable for OnboardingUpsell {
|
||||
const KEY: &'static str = "dismissed-trial-upsell";
|
||||
}
|
||||
|
||||
struct AgentLayoutOnboarding;
|
||||
|
||||
impl Dismissable for AgentLayoutOnboarding {
|
||||
const KEY: &'static str = "dismissed-agent-layout-onboarding";
|
||||
}
|
||||
|
||||
struct TrialEndUpsell;
|
||||
|
||||
impl Dismissable for TrialEndUpsell {
|
||||
|
||||
@@ -11,7 +11,7 @@ use gpui::{
|
||||
App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent,
|
||||
Render, SharedString, Task, WeakEntity, Window,
|
||||
};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use project::{AgentId, AgentRegistryStore, AgentServerStore};
|
||||
use release_channel::ReleaseChannel;
|
||||
use remote::RemoteConnectionOptions;
|
||||
@@ -275,8 +275,12 @@ impl ThreadImportModal {
|
||||
fn show_imported_threads_toast(&self, imported_count: usize, cx: &mut App) {
|
||||
let status_toast = if imported_count == 0 {
|
||||
StatusToast::new("No threads found to import.", cx, |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Info).color(Color::Muted))
|
||||
.dismiss_button(true)
|
||||
this.icon(
|
||||
Icon::new(IconName::Info)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
})
|
||||
} else {
|
||||
let message = if imported_count == 1 {
|
||||
@@ -285,8 +289,12 @@ impl ThreadImportModal {
|
||||
format!("Imported {imported_count} threads.")
|
||||
};
|
||||
StatusToast::new(message, cx, |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
|
||||
.dismiss_button(true)
|
||||
this.icon(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
})
|
||||
};
|
||||
|
||||
@@ -660,7 +668,7 @@ fn show_cross_channel_import_toast(
|
||||
) {
|
||||
let status_toast = if imported_count == 0 {
|
||||
StatusToast::new("No new threads found to import.", cx, |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Info).color(Color::Muted))
|
||||
this.icon(Icon::new(IconName::Info).color(Color::Muted))
|
||||
.dismiss_button(true)
|
||||
})
|
||||
} else {
|
||||
@@ -670,7 +678,7 @@ fn show_cross_channel_import_toast(
|
||||
format!("Imported {imported_count} threads from other channels.")
|
||||
};
|
||||
StatusToast::new(message, cx, |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
|
||||
this.icon(Icon::new(IconName::Check).color(Color::Success))
|
||||
.dismiss_button(true)
|
||||
})
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use action_log::ActionLog;
|
||||
use gpui::{App, Entity};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -11,15 +11,19 @@ pub fn show_undo_reject_toast(
|
||||
) {
|
||||
let action_log_weak = action_log.downgrade();
|
||||
let status_toast = StatusToast::new("Agent Changes Rejected", cx, move |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Undo).color(Color::Muted))
|
||||
.action("Undo", move |_window, cx| {
|
||||
if let Some(action_log) = action_log_weak.upgrade() {
|
||||
action_log
|
||||
.update(cx, |action_log, cx| action_log.undo_last_reject(cx))
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.dismiss_button(true)
|
||||
this.icon(
|
||||
Icon::new(IconName::Undo)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.action("Undo", move |_window, cx| {
|
||||
if let Some(action_log) = action_log_weak.upgrade() {
|
||||
action_log
|
||||
.update(cx, |action_log, cx| action_log.undo_last_reject(cx))
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.dismiss_button(true)
|
||||
});
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
}
|
||||
@@ -17,9 +17,7 @@ use std::sync::Arc;
|
||||
|
||||
use client::{Client, UserStore, zed_urls};
|
||||
use gpui::{AnyElement, Entity, IntoElement, ParentElement};
|
||||
use ui::{
|
||||
Divider, List, ListBulletItem, RegisterComponent, Tooltip, Vector, VectorName, prelude::*,
|
||||
};
|
||||
use ui::{Divider, RegisterComponent, Tooltip, Vector, VectorName, prelude::*};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum SignInStatus {
|
||||
@@ -442,131 +440,3 @@ impl Component for ZedAiOnboarding {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct AgentLayoutOnboarding {
|
||||
pub use_agent_layout: Arc<dyn Fn(&mut Window, &mut App)>,
|
||||
pub revert_to_editor_layout: Arc<dyn Fn(&mut Window, &mut App)>,
|
||||
pub dismissed: Arc<dyn Fn(&mut Window, &mut App)>,
|
||||
pub is_agent_layout: bool,
|
||||
}
|
||||
|
||||
impl Render for AgentLayoutOnboarding {
|
||||
fn render(&mut self, _window: &mut ui::Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let description = "With the new Threads Sidebar, you can manage multiple agents across several projects, all in one window.";
|
||||
|
||||
let dismiss_button = div().absolute().top_0().right_0().child(
|
||||
IconButton::new("dismiss", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let dismiss = self.dismissed.clone();
|
||||
move |_, window, cx| {
|
||||
telemetry::event!("Agentic Layout Onboarding Dismissed");
|
||||
dismiss(window, cx)
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let primary_button = if self.is_agent_layout {
|
||||
Button::new("revert", "Use Previous Layout")
|
||||
.label_size(LabelSize::Small)
|
||||
.style(ButtonStyle::Outlined)
|
||||
.on_click({
|
||||
let revert = self.revert_to_editor_layout.clone();
|
||||
let dismiss = self.dismissed.clone();
|
||||
move |_, window, cx| {
|
||||
telemetry::event!("Clicked to Use Previous Layout");
|
||||
revert(window, cx);
|
||||
dismiss(window, cx);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Button::new("start", "Use New Layout")
|
||||
.label_size(LabelSize::Small)
|
||||
.style(ButtonStyle::Outlined)
|
||||
.on_click({
|
||||
let use_layout = self.use_agent_layout.clone();
|
||||
let dismiss = self.dismissed.clone();
|
||||
move |_, window, cx| {
|
||||
telemetry::event!("Clicked to Use New Layout");
|
||||
use_layout(window, cx);
|
||||
dismiss(window, cx);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let content = v_flex()
|
||||
.min_w_0()
|
||||
.w_full()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(Label::new("A new workspace layout for agentic workflows"))
|
||||
.child(Label::new(description).color(Color::Muted).mb_2())
|
||||
.child(
|
||||
List::new()
|
||||
.child(ListBulletItem::new(
|
||||
"The Sidebar and Agent Panel are on the left by default",
|
||||
))
|
||||
.child(ListBulletItem::new(
|
||||
"The Project Panel and all other panels shift to the right",
|
||||
))
|
||||
.child(ListBulletItem::new(
|
||||
"You can always customize your workspace layout in your Settings",
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.flex_wrap()
|
||||
.justify_end()
|
||||
.child(
|
||||
Button::new("learn", "Learn More")
|
||||
.label_size(LabelSize::Small)
|
||||
.style(ButtonStyle::OutlinedGhost)
|
||||
.on_click(move |_, _, cx| {
|
||||
cx.open_url(&zed_urls::parallel_agents_blog(cx))
|
||||
}),
|
||||
)
|
||||
.child(primary_button),
|
||||
)
|
||||
.child(dismiss_button);
|
||||
|
||||
AgentPanelOnboardingCard::new().child(content)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for AgentLayoutOnboarding {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Onboarding
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"Agent Layout Onboarding"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let onboarding = cx.new(|_cx| AgentLayoutOnboarding {
|
||||
use_agent_layout: Arc::new(|_, _| {}),
|
||||
revert_to_editor_layout: Arc::new(|_, _| {}),
|
||||
dismissed: Arc::new(|_, _| {}),
|
||||
is_agent_layout: false,
|
||||
});
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.min_w_0()
|
||||
.gap_4()
|
||||
.child(single_example(
|
||||
"Agent Layout Onboarding",
|
||||
div()
|
||||
.w_full()
|
||||
.min_w_40()
|
||||
.max_w(px(1100.))
|
||||
.child(onboarding)
|
||||
.into_any_element(),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ client.workspace = true
|
||||
db.workspace = true
|
||||
fs.workspace = true
|
||||
editor.workspace = true
|
||||
notifications.workspace = true
|
||||
gpui.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
release_channel.workspace = true
|
||||
|
||||
@@ -9,6 +9,7 @@ use gpui::{
|
||||
App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Window, actions, prelude::*,
|
||||
};
|
||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
@@ -207,17 +208,17 @@ fn announcement_for_version(version: &Version, cx: &App) -> Option<AnnouncementC
|
||||
let fs = <dyn Fs>::global(cx);
|
||||
Some(AnnouncementContent {
|
||||
heading: "Introducing Parallel Agents".into(),
|
||||
description: "Run multiple agent threads simultaneously across projects.".into(),
|
||||
description: "Run multiple threads of your favorite agents simultaneously across projects in a new workspace layout, tailored for agentic workflows.".into(),
|
||||
bullet_items: vec![
|
||||
"Use your favorite agents in parallel".into(),
|
||||
"Optionally isolate agents using worktrees".into(),
|
||||
"Combine multiple projects in one window".into(),
|
||||
],
|
||||
primary_action_label: "Try Now".into(),
|
||||
primary_action_label: "Try Agentic Layout".into(),
|
||||
primary_action_url: None,
|
||||
primary_action_callback: Some(Arc::new(move |window, cx| {
|
||||
let already_agent_layout =
|
||||
matches!(AgentSettings::get_layout(cx), WindowLayout::Agent(_));
|
||||
let get_layout = AgentSettings::get_layout(cx);
|
||||
let already_agent_layout = matches!(get_layout, WindowLayout::Agent(_));
|
||||
|
||||
let update;
|
||||
if !already_agent_layout {
|
||||
@@ -230,6 +231,7 @@ fn announcement_for_version(version: &Version, cx: &App) -> Option<AnnouncementC
|
||||
update = None;
|
||||
}
|
||||
|
||||
let revert_fs = fs.clone();
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
if let Some(update) = update {
|
||||
@@ -237,6 +239,35 @@ fn announcement_for_version(version: &Version, cx: &App) -> Option<AnnouncementC
|
||||
}
|
||||
|
||||
cx.update(|window, cx| {
|
||||
if !already_agent_layout {
|
||||
if let Some(workspace) = Workspace::for_window(window, cx) {
|
||||
let toast = StatusToast::new(
|
||||
"You are in the new agentic layout!",
|
||||
cx,
|
||||
move |this, _cx| {
|
||||
this.icon(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.action("Revert", move |_window, cx| {
|
||||
let _ = AgentSettings::set_layout(
|
||||
get_layout.clone(),
|
||||
revert_fs.clone(),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.auto_dismiss(false)
|
||||
.dismiss_button(true)
|
||||
},
|
||||
);
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.toggle_status_toast(toast, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.dispatch_action(Box::new(FocusWorkspaceSidebar), cx);
|
||||
window.dispatch_action(Box::new(FocusAgent), cx);
|
||||
})
|
||||
@@ -381,8 +412,10 @@ pub fn notify_if_app_was_updated(cx: &mut App) {
|
||||
}
|
||||
|
||||
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let should_show_notification = should_show_notification.await?;
|
||||
|
||||
if should_show_notification {
|
||||
cx.update(|cx| {
|
||||
show_update_notification(cx);
|
||||
|
||||
@@ -8,7 +8,7 @@ use gpui::{
|
||||
};
|
||||
use gpui::{ListState, ScrollHandle, ScrollStrategy, UniformListScrollHandle};
|
||||
use language::LanguageRegistry;
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use persistence::ComponentPreviewDb;
|
||||
use project::Project;
|
||||
use std::{iter::Iterator, ops::Range, sync::Arc};
|
||||
@@ -561,10 +561,14 @@ impl ComponentPreview {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let status_toast =
|
||||
StatusToast::new("`zed/new-notification-system` created!", cx, |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted))
|
||||
.action("Open Pull Request", |_, cx| {
|
||||
cx.open_url("https://github.com/")
|
||||
})
|
||||
this.icon(
|
||||
Icon::new(IconName::GitBranch)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.action("Open Pull Request", |_, cx| {
|
||||
cx.open_url("https://github.com/")
|
||||
})
|
||||
});
|
||||
workspace.toggle_status_toast(status_toast, cx)
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ use gpui::{
|
||||
Subscription, Task, TextStyle, UniformList, UniformListScrollHandle, WeakEntity, actions,
|
||||
anchored, deferred, uniform_list,
|
||||
};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use project::debugger::{MemoryCell, dap_command::DataBreakpointContext, session::Session};
|
||||
use settings::Settings;
|
||||
use theme_settings::ThemeSettings;
|
||||
@@ -480,7 +480,7 @@ impl MemoryView {
|
||||
cx.emit(DismissEvent)
|
||||
});
|
||||
}).detach();
|
||||
this.icon(ToastIcon::new(IconName::XCircle).color(Color::Error))
|
||||
this.icon(Icon::new(IconName::XCircle).size(IconSize::Small).color(Color::Error))
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{App, Context, WeakEntity, Window};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use std::sync::Arc;
|
||||
use ui::{Color, IconName, SharedString};
|
||||
use ui::{Color, Icon, IconName, IconSize, SharedString};
|
||||
use util::ResultExt;
|
||||
use workspace::{self, Workspace};
|
||||
|
||||
@@ -48,8 +48,12 @@ pub fn clone_and_open(
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let toast = StatusToast::new(error.to_string(), cx, |this, _| {
|
||||
this.icon(ToastIcon::new(IconName::XCircle).color(Color::Error))
|
||||
.dismiss_button(true)
|
||||
this.icon(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
});
|
||||
workspace.toggle_status_toast(toast, cx);
|
||||
})
|
||||
|
||||
@@ -50,7 +50,7 @@ use language_model::{
|
||||
};
|
||||
use menu;
|
||||
use multi_buffer::ExcerptBoundaryInfo;
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use panel::{PanelHeader, panel_button, panel_filled_button, panel_icon_button};
|
||||
use project::{
|
||||
Fs, Project, ProjectPath,
|
||||
@@ -3864,9 +3864,17 @@ impl GitPanel {
|
||||
let status_toast = StatusToast::new(message, cx, move |this, _cx| {
|
||||
use remote_output::SuccessStyle::*;
|
||||
match style {
|
||||
Toast => this.icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted)),
|
||||
Toast => this.icon(
|
||||
Icon::new(IconName::GitBranch)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
ToastWithLog { output } => this
|
||||
.icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted))
|
||||
.icon(
|
||||
Icon::new(IconName::GitBranch)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.action("View Log", move |window, cx| {
|
||||
let output = output.clone();
|
||||
let output =
|
||||
@@ -3878,7 +3886,11 @@ impl GitPanel {
|
||||
.ok();
|
||||
}),
|
||||
PushPrLink { text, link } => this
|
||||
.icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted))
|
||||
.icon(
|
||||
Icon::new(IconName::GitBranch)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.action(text, move |_, cx| cx.open_url(&link)),
|
||||
}
|
||||
.dismiss_button(true)
|
||||
@@ -6479,16 +6491,20 @@ pub(crate) fn show_error_toast(
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let workspace_weak = cx.weak_entity();
|
||||
let toast = StatusToast::new(format!("git {} failed", action), cx, |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::XCircle).color(Color::Error))
|
||||
.action("View Log", move |window, cx| {
|
||||
let message = message.clone();
|
||||
let action = action.clone();
|
||||
workspace_weak
|
||||
.update(cx, move |workspace, cx| {
|
||||
open_output(action, workspace, &message, window, cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
this.icon(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.action("View Log", move |window, cx| {
|
||||
let message = message.clone();
|
||||
let action = action.clone();
|
||||
workspace_weak
|
||||
.update(cx, move |workspace, cx| {
|
||||
open_output(action, workspace, &message, window, cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
});
|
||||
workspace.toggle_status_toast(toast, cx)
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Language, LanguageConfig, ToOffset as _};
|
||||
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use project::{CompletionDisplayOptions, Project};
|
||||
use settings::{
|
||||
BaseKeymap, KeybindSource, KeymapFile, Settings as _, SettingsAssets, infer_json_indent_size,
|
||||
@@ -2883,8 +2883,12 @@ impl KeybindingEditorModal {
|
||||
format!("Saved edits to the {} action.", humanized_action_name),
|
||||
cx,
|
||||
move |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
|
||||
.dismiss_button(true)
|
||||
this.icon(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
// .action("Undo", f) todo: wire the undo functionality
|
||||
},
|
||||
);
|
||||
|
||||
@@ -5,41 +5,13 @@ use ui::{Tooltip, prelude::*};
|
||||
use workspace::{ToastAction, ToastView};
|
||||
use zed_actions::toast;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ToastIcon {
|
||||
icon: IconName,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl ToastIcon {
|
||||
pub fn new(icon: IconName) -> Self {
|
||||
Self {
|
||||
icon,
|
||||
color: Color::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IconName> for ToastIcon {
|
||||
fn from(icon: IconName) -> Self {
|
||||
Self {
|
||||
icon,
|
||||
color: Color::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct StatusToast {
|
||||
icon: Option<ToastIcon>,
|
||||
icon: Option<Icon>,
|
||||
text: SharedString,
|
||||
action: Option<ToastAction>,
|
||||
show_dismiss: bool,
|
||||
auto_dismiss: bool,
|
||||
this_handle: Entity<Self>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
@@ -59,6 +31,7 @@ impl StatusToast {
|
||||
icon: None,
|
||||
action: None,
|
||||
show_dismiss: false,
|
||||
auto_dismiss: true,
|
||||
this_handle: cx.entity(),
|
||||
focus_handle,
|
||||
},
|
||||
@@ -67,11 +40,16 @@ impl StatusToast {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn icon(mut self, icon: ToastIcon) -> Self {
|
||||
pub fn icon(mut self, icon: Icon) -> Self {
|
||||
self.icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn auto_dismiss(mut self, auto_dismiss: bool) -> Self {
|
||||
self.auto_dismiss = auto_dismiss;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn action(
|
||||
mut self,
|
||||
label: impl Into<SharedString>,
|
||||
@@ -116,9 +94,7 @@ impl Render for StatusToast {
|
||||
.flex_none()
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.shadow_lg()
|
||||
.when_some(self.icon.as_ref(), |this, icon| {
|
||||
this.child(Icon::new(icon.icon).color(icon.color))
|
||||
})
|
||||
.when_some(self.icon.clone(), |this, icon| this.child(icon))
|
||||
.child(Label::new(self.text.clone()).color(Color::Default))
|
||||
.when_some(self.action.as_ref(), |this, action| {
|
||||
this.child(
|
||||
@@ -155,6 +131,10 @@ impl ToastView for StatusToast {
|
||||
fn action(&self) -> Option<ToastAction> {
|
||||
self.action.clone()
|
||||
}
|
||||
|
||||
fn auto_dismiss(&self) -> bool {
|
||||
self.auto_dismiss
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for StatusToast {
|
||||
@@ -183,33 +163,55 @@ impl Component for StatusToast {
|
||||
let icon_example = StatusToast::new(
|
||||
"Nathan Sobo accepted your contact request",
|
||||
cx,
|
||||
|this, _| this.icon(ToastIcon::new(IconName::Check).color(Color::Muted)),
|
||||
|this, _| {
|
||||
this.icon(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
let success_example = StatusToast::new("Pushed 4 changes to `zed/main`", cx, |this, _| {
|
||||
this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
|
||||
this.icon(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
});
|
||||
|
||||
let error_example = StatusToast::new(
|
||||
"git push: Couldn't find remote origin `iamnbutler/zed`",
|
||||
cx,
|
||||
|this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::XCircle).color(Color::Error))
|
||||
.action("More Info", |_, _| {})
|
||||
this.icon(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.action("More Info", |_, _| {})
|
||||
},
|
||||
);
|
||||
|
||||
let warning_example = StatusToast::new("You have outdated settings", cx, |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Warning).color(Color::Warning))
|
||||
.action("More Info", |_, _| {})
|
||||
this.icon(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Warning),
|
||||
)
|
||||
.action("More Info", |_, _| {})
|
||||
});
|
||||
|
||||
let pr_example =
|
||||
StatusToast::new("`zed/new-notification-system` created!", cx, |this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::GitBranch).color(Color::Muted))
|
||||
.action("Open Pull Request", |_, cx| {
|
||||
cx.open_url("https://github.com/")
|
||||
})
|
||||
this.icon(
|
||||
Icon::new(IconName::GitBranch)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.action("Open Pull Request", |_, cx| {
|
||||
cx.open_url("https://github.com/")
|
||||
})
|
||||
});
|
||||
|
||||
Some(
|
||||
|
||||
@@ -7,7 +7,7 @@ use gpui::{
|
||||
FocusHandle, Focusable, Global, IntoElement, KeyContext, Render, ScrollHandle, SharedString,
|
||||
Subscription, Task, WeakEntity, Window, actions,
|
||||
};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{SettingsStore, VsCodeSettingsSource};
|
||||
@@ -495,8 +495,12 @@ pub async fn handle_import_vscode_settings(
|
||||
format!("Your {} settings were successfully imported.", source),
|
||||
cx,
|
||||
|this, _| {
|
||||
this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
|
||||
.dismiss_button(true)
|
||||
this.icon(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
},
|
||||
);
|
||||
SettingsImportState::update(cx, |state, _| match source {
|
||||
@@ -514,11 +518,15 @@ pub async fn handle_import_vscode_settings(
|
||||
"Failed to import settings. See log for details",
|
||||
cx,
|
||||
|this, _| {
|
||||
this.icon(ToastIcon::new(IconName::Close).color(Color::Error))
|
||||
.action("Open Log", |window, cx| {
|
||||
window.dispatch_action(workspace::OpenLog.boxed_clone(), cx)
|
||||
})
|
||||
.dismiss_button(true)
|
||||
this.icon(
|
||||
Icon::new(IconName::Close)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.action("Open Log", |window, cx| {
|
||||
window.dispatch_action(workspace::OpenLog.boxed_clone(), cx)
|
||||
})
|
||||
.dismiss_button(true)
|
||||
},
|
||||
);
|
||||
workspace.toggle_status_toast(error_toast, cx);
|
||||
|
||||
@@ -31,7 +31,7 @@ use gpui::{
|
||||
};
|
||||
use language::DiagnosticSeverity;
|
||||
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use project::{
|
||||
Entry, EntryKind, Fs, GitEntry, GitEntryRef, GitTraversal, Project, ProjectEntryId,
|
||||
ProjectPath, Worktree, WorktreeId,
|
||||
@@ -2275,8 +2275,12 @@ impl ProjectPanel {
|
||||
.update(cx, |panel, cx| {
|
||||
let message = format!("Failed to restore {}: {}", file_name, e);
|
||||
let toast = StatusToast::new(message, cx, |this, _| {
|
||||
this.icon(ToastIcon::new(IconName::XCircle).color(Color::Error))
|
||||
.dismiss_button(true)
|
||||
this.icon(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
});
|
||||
panel
|
||||
.workspace
|
||||
|
||||
@@ -15,32 +15,39 @@ impl RenderOnce for ParallelAgentsIllustration {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let icon_container = || h_flex().size_4().flex_shrink_0().justify_center();
|
||||
|
||||
let title_bar = |id: &'static str, width: DefiniteLength, duration_ms: u64| {
|
||||
let loading_bar = |id: &'static str, width: DefiniteLength, duration_ms: u64| {
|
||||
div()
|
||||
.h_2()
|
||||
.h(rems_from_px(5.))
|
||||
.w(width)
|
||||
.rounded_full()
|
||||
.debug_bg_blue()
|
||||
.bg(cx.theme().colors().element_selected)
|
||||
.with_animation(
|
||||
id,
|
||||
Animation::new(Duration::from_millis(duration_ms))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.4, 0.8)),
|
||||
.with_easing(pulsating_between(0.1, 0.8)),
|
||||
|label, delta| label.opacity(delta),
|
||||
)
|
||||
};
|
||||
|
||||
let skeleton_bar = |width: DefiniteLength| {
|
||||
div().h(rems_from_px(5.)).w(width).rounded_full().bg(cx
|
||||
.theme()
|
||||
.colors()
|
||||
.text_muted
|
||||
.opacity(0.05))
|
||||
};
|
||||
|
||||
let time =
|
||||
|time: SharedString| Label::new(time).size(LabelSize::XSmall).color(Color::Muted);
|
||||
|
||||
let worktree = |worktree: SharedString| {
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Icon::new(IconName::GitWorktree)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
.size(IconSize::Indicator),
|
||||
)
|
||||
.child(
|
||||
Label::new(worktree)
|
||||
@@ -56,51 +63,53 @@ impl RenderOnce for ParallelAgentsIllustration {
|
||||
.alpha(0.5)
|
||||
};
|
||||
|
||||
let agent = |id: &'static str,
|
||||
icon: IconName,
|
||||
width: DefiniteLength,
|
||||
duration_ms: u64,
|
||||
data: Vec<AnyElement>| {
|
||||
let agent = |title: SharedString, icon: IconName, selected: bool, data: Vec<AnyElement>| {
|
||||
v_flex()
|
||||
.p_2()
|
||||
.when(selected, |this| {
|
||||
this.bg(cx.theme().colors().element_active.opacity(0.2))
|
||||
})
|
||||
.p_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
icon_container()
|
||||
.child(Icon::new(icon).size(IconSize::Small).color(Color::Muted)),
|
||||
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted)),
|
||||
)
|
||||
.child(title_bar(id, width, duration_ms)),
|
||||
.map(|this| {
|
||||
if selected {
|
||||
this.child(
|
||||
Label::new(title)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
} else {
|
||||
this.child(skeleton_bar(relative(0.7)))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.opacity(0.8)
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.gap_1()
|
||||
.child(icon_container())
|
||||
.children(data),
|
||||
)
|
||||
};
|
||||
|
||||
let agents = v_flex()
|
||||
.absolute()
|
||||
.w(rems_from_px(380.))
|
||||
.top_8()
|
||||
.rounded_t_sm()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.col_span(3)
|
||||
.bg(cx.theme().colors().elevated_surface_background)
|
||||
.shadow_md()
|
||||
.child(agent(
|
||||
"zed-agent-bar",
|
||||
"Fix branch label".into(),
|
||||
IconName::ZedAgent,
|
||||
relative(0.7),
|
||||
1800,
|
||||
true,
|
||||
vec![
|
||||
worktree("happy-tree".into()).into_any_element(),
|
||||
worktree("bug-fix".into()).into_any_element(),
|
||||
dot_separator().into_any_element(),
|
||||
DiffStat::new("ds", 23, 13)
|
||||
DiffStat::new("ds", 5, 2)
|
||||
.label_size(LabelSize::XSmall)
|
||||
.into_any_element(),
|
||||
dot_separator().into_any_element(),
|
||||
@@ -109,10 +118,9 @@ impl RenderOnce for ParallelAgentsIllustration {
|
||||
))
|
||||
.child(Divider::horizontal())
|
||||
.child(agent(
|
||||
"claude-bar",
|
||||
"Improve thread id".into(),
|
||||
IconName::AiClaude,
|
||||
relative(0.85),
|
||||
2400,
|
||||
false,
|
||||
vec![
|
||||
DiffStat::new("ds", 120, 84)
|
||||
.label_size(LabelSize::XSmall)
|
||||
@@ -123,27 +131,142 @@ impl RenderOnce for ParallelAgentsIllustration {
|
||||
))
|
||||
.child(Divider::horizontal())
|
||||
.child(agent(
|
||||
"openai-bar",
|
||||
"Refactor archive view".into(),
|
||||
IconName::AiOpenAi,
|
||||
relative(0.4),
|
||||
3100,
|
||||
false,
|
||||
vec![
|
||||
worktree("silent-forest".into()).into_any_element(),
|
||||
dot_separator().into_any_element(),
|
||||
time("37m".into()).into_any_element(),
|
||||
],
|
||||
))
|
||||
.child(Divider::horizontal());
|
||||
));
|
||||
|
||||
let thread_view = v_flex()
|
||||
.col_span(3)
|
||||
.h_full()
|
||||
.flex_1()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.child(
|
||||
h_flex()
|
||||
.px_1p5()
|
||||
.py_0p5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.child(
|
||||
Label::new("Fix branch label")
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::Plus)
|
||||
.size(IconSize::Indicator)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().p_1().child(
|
||||
v_flex()
|
||||
.px_1()
|
||||
.py_1p5()
|
||||
.gap_1()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.rounded_sm()
|
||||
.shadow_sm()
|
||||
.child(skeleton_bar(relative(0.7)))
|
||||
.child(skeleton_bar(relative(0.2))),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.p_2()
|
||||
.gap_1()
|
||||
.child(loading_bar("a", relative(0.55), 2200))
|
||||
.child(loading_bar("b", relative(0.75), 2000))
|
||||
.child(loading_bar("c", relative(0.25), 2400)),
|
||||
);
|
||||
|
||||
let file_row = |indent: usize, is_folder: bool, bar_width: Rems| {
|
||||
let indent_px = rems_from_px((indent as f32) * 4.0);
|
||||
|
||||
h_flex()
|
||||
.px_2()
|
||||
.py_px()
|
||||
.gap_1()
|
||||
.pl(indent_px)
|
||||
.child(
|
||||
icon_container().child(
|
||||
Icon::new(if is_folder {
|
||||
IconName::FolderOpen
|
||||
} else {
|
||||
IconName::FileRust
|
||||
})
|
||||
.size(IconSize::Indicator)
|
||||
.color(Color::Custom(cx.theme().colors().icon_muted.opacity(0.2))),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().h_1p5().w(bar_width).rounded_sm().bg(cx
|
||||
.theme()
|
||||
.colors()
|
||||
.text
|
||||
.opacity(if is_folder { 0.15 } else { 0.1 })),
|
||||
)
|
||||
};
|
||||
|
||||
let project_panel = v_flex()
|
||||
.col_span(1)
|
||||
.h_full()
|
||||
.flex_1()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.child(
|
||||
v_flex()
|
||||
.child(file_row(0, true, rems_from_px(42.0)))
|
||||
.child(file_row(1, true, rems_from_px(28.0)))
|
||||
.child(file_row(2, false, rems_from_px(52.0)))
|
||||
.child(file_row(2, false, rems_from_px(36.0)))
|
||||
.child(file_row(2, false, rems_from_px(44.0)))
|
||||
.child(file_row(1, true, rems_from_px(34.0)))
|
||||
.child(file_row(2, false, rems_from_px(48.0)))
|
||||
.child(file_row(2, true, rems_from_px(26.0)))
|
||||
.child(file_row(3, false, rems_from_px(40.0)))
|
||||
.child(file_row(3, false, rems_from_px(56.0)))
|
||||
.child(file_row(1, false, rems_from_px(38.0)))
|
||||
.child(file_row(0, true, rems_from_px(30.0)))
|
||||
.child(file_row(1, false, rems_from_px(46.0)))
|
||||
.child(file_row(1, false, rems_from_px(32.0))),
|
||||
);
|
||||
|
||||
let workspace = div()
|
||||
.absolute()
|
||||
.top_8()
|
||||
.grid()
|
||||
.grid_cols(7)
|
||||
.w(rems_from_px(380.))
|
||||
.rounded_t_sm()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.shadow_md()
|
||||
.child(agents)
|
||||
.child(thread_view)
|
||||
.child(project_panel);
|
||||
|
||||
h_flex()
|
||||
.relative()
|
||||
.h(rems_from_px(180.))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.bg(cx.theme().colors().editor_background.opacity(0.6))
|
||||
.justify_center()
|
||||
.items_end()
|
||||
.rounded_t_md()
|
||||
.overflow_hidden()
|
||||
.bg(gpui::black().opacity(0.2))
|
||||
.child(agents)
|
||||
.child(workspace)
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,7 @@ impl From<IconName> for Icon {
|
||||
}
|
||||
|
||||
/// The source of an icon.
|
||||
#[derive(Clone)]
|
||||
enum IconSource {
|
||||
/// An SVG embedded in the Zed binary.
|
||||
Embedded(SharedString),
|
||||
@@ -126,7 +127,7 @@ enum IconSource {
|
||||
ExternalSvg(SharedString),
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(Clone, IntoElement, RegisterComponent)]
|
||||
pub struct Icon {
|
||||
source: IconSource,
|
||||
color: Color,
|
||||
|
||||
@@ -44,6 +44,10 @@ pub fn init(cx: &mut App) {
|
||||
|
||||
pub trait ToastView: ManagedView {
|
||||
fn action(&self) -> Option<ToastAction>;
|
||||
|
||||
fn auto_dismiss(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -131,6 +135,7 @@ impl ToastLayer {
|
||||
V: ToastView,
|
||||
{
|
||||
let action = new_toast.read(cx).action();
|
||||
let auto_dismiss = new_toast.read(cx).auto_dismiss();
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
self.active_toast = Some(ActiveToast {
|
||||
@@ -143,7 +148,9 @@ impl ToastLayer {
|
||||
focus_handle,
|
||||
});
|
||||
|
||||
self.start_dismiss_timer(DEFAULT_TOAST_DURATION, cx);
|
||||
if auto_dismiss {
|
||||
self.start_dismiss_timer(DEFAULT_TOAST_DURATION, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user