Detailed changes
@@ -108,7 +108,6 @@ dependencies = [
"project",
"proto",
"release_channel",
- "semver",
"smallvec",
"ui",
"util",
@@ -17247,9 +17246,11 @@ dependencies = [
"pretty_assertions",
"project",
"recent_projects",
+ "release_channel",
"remote",
"rpc",
"schemars",
+ "semver",
"serde",
"settings",
"smallvec",
@@ -23,7 +23,6 @@ gpui.workspace = true
language.workspace = true
project.workspace = true
proto.workspace = true
-semver.workspace = true
smallvec.workspace = true
ui.workspace = true
util.workspace = true
@@ -1,4 +1,4 @@
-use auto_update::{AutoUpdateStatus, AutoUpdater, DismissMessage, VersionCheckType};
+use auto_update::DismissMessage;
use editor::Editor;
use extension_host::{ExtensionOperation, ExtensionStore};
use futures::StreamExt;
@@ -49,7 +49,6 @@ pub enum Event {
pub struct ActivityIndicator {
statuses: Vec<ServerStatus>,
project: Entity<Project>,
- auto_updater: Option<Entity<AutoUpdater>>,
context_menu_handle: PopoverMenuHandle<ContextMenu>,
fs_jobs: Vec<fs::JobInfo>,
}
@@ -82,7 +81,6 @@ impl ActivityIndicator {
cx: &mut Context<Workspace>,
) -> Entity<ActivityIndicator> {
let project = workspace.project().clone();
- let auto_updater = AutoUpdater::get(cx);
let this = cx.new(|cx| {
let mut status_events = languages.language_server_binary_statuses();
cx.spawn(async move |this, cx| {
@@ -215,14 +213,9 @@ impl ActivityIndicator {
)
.detach();
- if let Some(auto_updater) = auto_updater.as_ref() {
- cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
- }
-
Self {
statuses: Vec::new(),
project: project.clone(),
- auto_updater,
context_menu_handle: PopoverMenuHandle::default(),
fs_jobs: Vec::new(),
}
@@ -302,15 +295,6 @@ impl ActivityIndicator {
}
fn dismiss_message(&mut self, _: &DismissMessage, _: &mut Window, cx: &mut Context<Self>) {
- let dismissed = if let Some(updater) = &self.auto_updater {
- updater.update(cx, |updater, cx| updater.dismiss(cx))
- } else {
- false
- };
- if dismissed {
- return;
- }
-
self.project.update(cx, |project, cx| {
if project.last_formatting_failure(cx).is_some() {
project.reset_last_formatting_failure(cx);
@@ -669,122 +653,47 @@ impl ActivityIndicator {
});
}
- // Show any application auto-update info.
- self.auto_updater
- .as_ref()
- .and_then(|updater| match &updater.read(cx).status() {
- AutoUpdateStatus::Checking => Some(Content {
- icon: Some(
- Icon::new(IconName::LoadCircle)
- .size(IconSize::Small)
- .with_rotate_animation(3)
- .into_any_element(),
- ),
- message: "Checking for Zed updates…".to_string(),
- on_click: Some(Arc::new(|this, window, cx| {
- this.dismiss_message(&DismissMessage, window, cx)
- })),
- tooltip_message: None,
- }),
- AutoUpdateStatus::Downloading { version } => Some(Content {
- icon: Some(
- Icon::new(IconName::Download)
- .size(IconSize::Small)
- .into_any_element(),
- ),
- message: "Downloading Zed update…".to_string(),
- on_click: Some(Arc::new(|this, window, cx| {
- this.dismiss_message(&DismissMessage, window, cx)
- })),
- tooltip_message: Some(Self::version_tooltip_message(version)),
- }),
- AutoUpdateStatus::Installing { version } => Some(Content {
- icon: Some(
- Icon::new(IconName::LoadCircle)
- .size(IconSize::Small)
- .with_rotate_animation(3)
- .into_any_element(),
- ),
- message: "Installing Zed update…".to_string(),
- on_click: Some(Arc::new(|this, window, cx| {
- this.dismiss_message(&DismissMessage, window, cx)
- })),
- tooltip_message: Some(Self::version_tooltip_message(version)),
- }),
- AutoUpdateStatus::Updated { version } => Some(Content {
- icon: None,
- message: "Click to restart and update Zed".to_string(),
- on_click: Some(Arc::new(move |_, _, cx| workspace::reload(cx))),
- tooltip_message: Some(Self::version_tooltip_message(version)),
- }),
- AutoUpdateStatus::Errored { error } => Some(Content {
- icon: Some(
- Icon::new(IconName::Warning)
- .size(IconSize::Small)
- .into_any_element(),
- ),
- message: "Failed to update Zed".to_string(),
- on_click: Some(Arc::new(|this, window, cx| {
- window.dispatch_action(Box::new(workspace::OpenLog), cx);
- this.dismiss_message(&DismissMessage, window, cx);
- })),
- tooltip_message: Some(format!("{error}")),
- }),
- AutoUpdateStatus::Idle => None,
- })
- .or_else(|| {
- if let Some(extension_store) =
- ExtensionStore::try_global(cx).map(|extension_store| extension_store.read(cx))
- && let Some((extension_id, operation)) =
- extension_store.outstanding_operations().iter().next()
- {
- let (message, icon, rotate) = match operation {
- ExtensionOperation::Install => (
- format!("Installing {extension_id} extension…"),
- IconName::LoadCircle,
- true,
- ),
- ExtensionOperation::Upgrade => (
- format!("Updating {extension_id} extension…"),
- IconName::Download,
- false,
- ),
- ExtensionOperation::Remove => (
- format!("Removing {extension_id} extension…"),
- IconName::LoadCircle,
- true,
- ),
- };
+ // Show any extension installation info.
+ if let Some(extension_store) =
+ ExtensionStore::try_global(cx).map(|extension_store| extension_store.read(cx))
+ && let Some((extension_id, operation)) =
+ extension_store.outstanding_operations().iter().next()
+ {
+ let (message, icon, rotate) = match operation {
+ ExtensionOperation::Install => (
+ format!("Installing {extension_id} extension…"),
+ IconName::LoadCircle,
+ true,
+ ),
+ ExtensionOperation::Upgrade => (
+ format!("Updating {extension_id} extension…"),
+ IconName::Download,
+ false,
+ ),
+ ExtensionOperation::Remove => (
+ format!("Removing {extension_id} extension…"),
+ IconName::LoadCircle,
+ true,
+ ),
+ };
- Some(Content {
- icon: Some(Icon::new(icon).size(IconSize::Small).map(|this| {
- if rotate {
- this.with_rotate_animation(3).into_any_element()
- } else {
- this.into_any_element()
- }
- })),
- message,
- on_click: Some(Arc::new(|this, window, cx| {
- this.dismiss_message(&Default::default(), window, cx)
- })),
- tooltip_message: None,
- })
- } else {
- None
- }
- })
- }
+ return Some(Content {
+ icon: Some(Icon::new(icon).size(IconSize::Small).map(|this| {
+ if rotate {
+ this.with_rotate_animation(3).into_any_element()
+ } else {
+ this.into_any_element()
+ }
+ })),
+ message,
+ on_click: Some(Arc::new(|this, window, cx| {
+ this.dismiss_message(&Default::default(), window, cx)
+ })),
+ tooltip_message: None,
+ });
+ }
- fn version_tooltip_message(version: &VersionCheckType) -> String {
- format!("Version: {}", {
- match version {
- auto_update::VersionCheckType::Sha(sha) => format!("{}…", sha.short()),
- auto_update::VersionCheckType::Semantic(semantic_version) => {
- semantic_version.to_string()
- }
- }
- })
+ None
}
fn toggle_language_server_work_context_menu(
@@ -922,26 +831,3 @@ impl StatusItemView for ActivityIndicator {
) {
}
}
-
-#[cfg(test)]
-mod tests {
- use release_channel::AppCommitSha;
- use semver::Version;
-
- use super::*;
-
- #[test]
- fn test_version_tooltip_message() {
- let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Semantic(
- Version::new(1, 0, 0),
- ));
-
- assert_eq!(message, "Version: 1.0.0");
-
- let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Sha(
- AppCommitSha::new("14d9a4189f058d8736339b06ff2340101eaea5af".to_string()),
- ));
-
- assert_eq!(message, "Version: 14d9a41…");
- }
-}
@@ -108,6 +108,7 @@ pub struct AutoUpdater {
client: Arc<Client>,
pending_poll: Option<Task<Option<()>>>,
quit_subscription: Option<gpui::Subscription>,
+ update_check_type: UpdateCheckType,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
@@ -318,11 +319,18 @@ impl InstallerDir {
}
}
+#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UpdateCheckType {
Automatic,
Manual,
}
+impl UpdateCheckType {
+ pub fn is_manual(self) -> bool {
+ self == Self::Manual
+ }
+}
+
impl AutoUpdater {
pub fn get(cx: &mut App) -> Option<Entity<Self>> {
cx.default_global::<GlobalAutoUpdate>().0.clone()
@@ -352,6 +360,7 @@ impl AutoUpdater {
client,
pending_poll: None,
quit_subscription,
+ update_check_type: UpdateCheckType::Automatic,
}
}
@@ -373,10 +382,15 @@ impl AutoUpdater {
})
}
+ pub fn update_check_type(&self) -> UpdateCheckType {
+ self.update_check_type
+ }
+
pub fn poll(&mut self, check_type: UpdateCheckType, cx: &mut Context<Self>) {
if self.pending_poll.is_some() {
return;
}
+ self.update_check_type = check_type;
cx.notify();
@@ -46,6 +46,7 @@ project.workspace = true
recent_projects.workspace = true
remote.workspace = true
rpc.workspace = true
+semver.workspace = true
schemars.workspace = true
serde.workspace = true
settings.workspace = true
@@ -70,8 +71,10 @@ http_client = { workspace = true, features = ["test-support"] }
notifications = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true
project = { workspace = true, features = ["test-support"] }
+release_channel.workspace = true
remote = { workspace = true, features = ["test-support"] }
rpc = { workspace = true, features = ["test-support"] }
+semver.workspace = true
settings = { workspace = true, features = ["test-support"] }
tree-sitter-md.workspace = true
util = { workspace = true, features = ["test-support"] }
@@ -3,6 +3,7 @@ pub mod collab;
mod onboarding_banner;
mod project_dropdown;
mod title_bar_settings;
+mod update_version;
#[cfg(feature = "stories")]
mod stories;
@@ -40,6 +41,7 @@ use ui::{
Avatar, ButtonLike, Chip, ContextMenu, IconWithIndicator, Indicator, PopoverMenu,
PopoverMenuHandle, TintColor, Tooltip, prelude::*,
};
+use update_version::UpdateVersion;
use util::ResultExt;
use workspace::{SwitchProject, ToggleWorktreeSecurity, Workspace, notifications::NotifyResultExt};
use zed_actions::OpenRemote;
@@ -61,7 +63,9 @@ actions!(
/// Toggles the project menu dropdown.
ToggleProjectMenu,
/// Switches to a different git branch.
- SwitchBranch
+ SwitchBranch,
+ /// A debug action to simulate an update being available to test the update banner UI.
+ SimulateUpdateAvailable
]
);
@@ -75,6 +79,17 @@ pub fn init(cx: &mut App) {
let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx));
workspace.set_titlebar_item(item.into(), window, cx);
+ workspace.register_action(|workspace, _: &SimulateUpdateAvailable, _window, cx| {
+ if let Some(titlebar) = workspace
+ .titlebar_item()
+ .and_then(|item| item.downcast::<TitleBar>().ok())
+ {
+ titlebar.update(cx, |titlebar, cx| {
+ titlebar.toggle_update_simulation(cx);
+ });
+ }
+ });
+
workspace.register_action(|workspace, _: &SwitchProject, window, cx| {
if let Some(titlebar) = workspace
.titlebar_item()
@@ -144,6 +159,7 @@ pub struct TitleBar {
application_menu: Option<Entity<ApplicationMenu>>,
_subscriptions: Vec<Subscription>,
banner: Entity<OnboardingBanner>,
+ update_version: Entity<UpdateVersion>,
screen_share_popover_handle: PopoverMenuHandle<ContextMenu>,
project_dropdown_handle: PopoverMenuHandle<ProjectDropdown>,
}
@@ -213,6 +229,7 @@ impl Render for TitleBar {
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.children(self.render_call_controls(window, cx))
.children(self.render_connection_status(status, cx))
+ .child(self.update_version.clone())
.when(
user.is_none() && TitleBarSettings::get_global(cx).show_sign_in,
|this| this.child(self.render_sign_in_button(cx)),
@@ -338,6 +355,7 @@ impl TitleBar {
.visible_when(|cx| !project::DisableAiSettings::get_global(cx).disable_ai)
});
+ let update_version = cx.new(|cx| UpdateVersion::new(cx));
let platform_titlebar = cx.new(|cx| PlatformTitleBar::new(id, cx));
Self {
@@ -349,6 +367,7 @@ impl TitleBar {
client,
_subscriptions: subscriptions,
banner,
+ update_version,
screen_share_popover_handle: PopoverMenuHandle::default(),
project_dropdown_handle: PopoverMenuHandle::default(),
}
@@ -358,6 +377,12 @@ impl TitleBar {
self.project.read(cx).visible_worktrees(cx).count()
}
+ fn toggle_update_simulation(&mut self, cx: &mut Context<Self>) {
+ self.update_version
+ .update(cx, |banner, cx| banner.update_simulation(cx));
+ cx.notify();
+ }
+
pub fn show_project_dropdown(&self, window: &mut Window, cx: &mut App) {
if self.worktree_count(cx) > 1 {
self.project_dropdown_handle.show(window, cx);
@@ -927,6 +952,8 @@ impl TitleBar {
}
pub fn render_user_menu_button(&mut self, cx: &mut Context<Self>) -> impl Element {
+ let show_update_badge = self.update_version.read(cx).show_update_in_menu_bar();
+
let user_store = self.user_store.read(cx);
let user = user_store.current_user();
@@ -990,6 +1017,27 @@ impl TitleBar {
)
.separator()
})
+ .when(show_update_badge, |this| {
+ this.custom_entry(
+ move |_window, _cx| {
+ h_flex()
+ .w_full()
+ .gap_1()
+ .justify_between()
+ .child(Label::new("Restart to update Zed").color(Color::Accent))
+ .child(
+ Icon::new(IconName::Download)
+ .size(IconSize::Small)
+ .color(Color::Accent),
+ )
+ .into_any_element()
+ },
+ move |_, cx| {
+ workspace::reload(cx);
+ },
+ )
+ .separator()
+ })
.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Keymap", Box::new(zed_actions::OpenKeymap))
.action(
@@ -1013,9 +1061,25 @@ impl TitleBar {
})
.map(|this| {
if is_signed_in && TitleBarSettings::get_global(cx).show_user_picture {
+ let avatar =
+ user_avatar
+ .clone()
+ .map(|avatar| Avatar::new(avatar))
+ .map(|avatar| {
+ if show_update_badge {
+ avatar.indicator(
+ div()
+ .absolute()
+ .bottom_0()
+ .right_0()
+ .child(Indicator::dot().color(Color::Accent)),
+ )
+ } else {
+ avatar
+ }
+ });
this.trigger_with_tooltip(
- ButtonLike::new("user-menu")
- .children(user_avatar.clone().map(|avatar| Avatar::new(avatar))),
+ ButtonLike::new("user-menu").children(avatar),
Tooltip::text("Toggle User Menu"),
)
} else {
@@ -0,0 +1,148 @@
+use std::sync::Arc;
+
+use anyhow::anyhow;
+use auto_update::{AutoUpdateStatus, AutoUpdater, UpdateCheckType, VersionCheckType};
+use gpui::{Empty, Render};
+use semver::Version;
+use ui::{UpdateButton, prelude::*};
+
+pub struct UpdateVersion {
+ status: AutoUpdateStatus,
+ update_check_type: UpdateCheckType,
+ dismissed: bool,
+}
+
+impl UpdateVersion {
+ pub fn new(cx: &mut Context<Self>) -> Self {
+ if let Some(auto_updater) = AutoUpdater::get(cx) {
+ cx.observe(&auto_updater, |this, auto_update, cx| {
+ this.status = auto_update.read(cx).status();
+ this.update_check_type = auto_update.read(cx).update_check_type();
+ if this.status.is_updated() {
+ this.dismissed = false;
+ }
+ })
+ .detach();
+ Self {
+ status: auto_updater.read(cx).status(),
+ update_check_type: UpdateCheckType::Automatic,
+ dismissed: false,
+ }
+ } else {
+ Self {
+ status: AutoUpdateStatus::Idle,
+ update_check_type: UpdateCheckType::Automatic,
+ dismissed: false,
+ }
+ }
+ }
+
+ pub fn update_simulation(&mut self, cx: &mut Context<Self>) {
+ let next_state = match self.status {
+ AutoUpdateStatus::Idle => AutoUpdateStatus::Checking,
+ AutoUpdateStatus::Checking => AutoUpdateStatus::Downloading {
+ version: VersionCheckType::Semantic(Version::new(1, 99, 0)),
+ },
+ AutoUpdateStatus::Downloading { .. } => AutoUpdateStatus::Installing {
+ version: VersionCheckType::Semantic(Version::new(1, 99, 0)),
+ },
+ AutoUpdateStatus::Installing { .. } => AutoUpdateStatus::Updated {
+ version: VersionCheckType::Semantic(Version::new(1, 99, 0)),
+ },
+ AutoUpdateStatus::Updated { .. } => AutoUpdateStatus::Errored {
+ error: Arc::new(anyhow!("Network timeout")),
+ },
+ AutoUpdateStatus::Errored { .. } => AutoUpdateStatus::Idle,
+ };
+
+ self.status = next_state;
+ self.update_check_type = UpdateCheckType::Manual;
+ self.dismissed = false;
+ cx.notify()
+ }
+
+ pub fn show_update_in_menu_bar(&self) -> bool {
+ self.dismissed && self.status.is_updated()
+ }
+
+ fn version_tooltip_message(version: &VersionCheckType) -> String {
+ format!("Version: {}", {
+ match version {
+ VersionCheckType::Sha(sha) => format!("{}…", sha.short()),
+ VersionCheckType::Semantic(semantic_version) => semantic_version.to_string(),
+ }
+ })
+ }
+}
+
+impl Render for UpdateVersion {
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ if self.dismissed {
+ return Empty.into_any_element();
+ }
+ match &self.status {
+ AutoUpdateStatus::Checking if self.update_check_type.is_manual() => {
+ UpdateButton::checking().into_any_element()
+ }
+ AutoUpdateStatus::Downloading { version } if self.update_check_type.is_manual() => {
+ let tooltip = Self::version_tooltip_message(&version);
+ UpdateButton::downloading(tooltip).into_any_element()
+ }
+ AutoUpdateStatus::Installing { version } if self.update_check_type.is_manual() => {
+ let tooltip = Self::version_tooltip_message(&version);
+ UpdateButton::installing(tooltip).into_any_element()
+ }
+ AutoUpdateStatus::Updated { version } => {
+ let tooltip = Self::version_tooltip_message(&version);
+ UpdateButton::updated(tooltip)
+ .on_click(|_, _, cx| {
+ workspace::reload(cx);
+ })
+ .on_dismiss(cx.listener(|this, _, _window, cx| {
+ this.dismissed = true;
+ cx.notify()
+ }))
+ .into_any_element()
+ }
+ AutoUpdateStatus::Errored { error } => {
+ let error_str = error.to_string();
+ UpdateButton::errored(error_str)
+ .on_click(|_, window, cx| {
+ window.dispatch_action(Box::new(workspace::OpenLog), cx);
+ })
+ .on_dismiss(cx.listener(|this, _, _window, cx| {
+ this.dismissed = true;
+ cx.notify()
+ }))
+ .into_any_element()
+ }
+ AutoUpdateStatus::Idle
+ | AutoUpdateStatus::Checking { .. }
+ | AutoUpdateStatus::Downloading { .. }
+ | AutoUpdateStatus::Installing { .. } => Empty.into_any_element(),
+ }
+ }
+}
+#[cfg(test)]
+mod tests {
+ use auto_update::VersionCheckType;
+ use release_channel::AppCommitSha;
+ use semver::Version;
+
+ use super::*;
+
+ #[test]
+ fn test_version_tooltip_message() {
+ let message = UpdateVersion::version_tooltip_message(&VersionCheckType::Semantic(
+ Version::new(1, 0, 0),
+ ));
+
+ assert_eq!(message, "Version: 1.0.0");
+
+ let message = UpdateVersion::version_tooltip_message(&VersionCheckType::Sha(
+ AppCommitSha::new("14d9a4189f058d8736339b06ff2340101eaea5af".to_string()),
+ ));
+
+ assert_eq!(message, "Version: 14d9a41…");
+ }
+}
@@ -1,3 +1,5 @@
mod collab_notification;
+mod update_button;
pub use collab_notification::*;
+pub use update_button::*;
@@ -0,0 +1,202 @@
+use gpui::{AnyElement, ClickEvent, prelude::*};
+
+use crate::{ButtonLike, CommonAnimationExt, Tooltip, prelude::*};
+
+/// A button component displayed in the title bar to show auto-update status.
+#[derive(IntoElement, RegisterComponent)]
+pub struct UpdateButton {
+ icon: IconName,
+ icon_animate: bool,
+ icon_color: Option<Color>,
+ message: SharedString,
+ tooltip: Option<SharedString>,
+ show_dismiss: bool,
+ on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
+ on_dismiss: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
+}
+
+impl UpdateButton {
+ pub fn new(icon: IconName, message: impl Into<SharedString>) -> Self {
+ Self {
+ icon,
+ icon_animate: false,
+ icon_color: None,
+ message: message.into(),
+ tooltip: None,
+ show_dismiss: false,
+ on_click: None,
+ on_dismiss: None,
+ }
+ }
+
+ /// Sets whether the icon should have a rotation animation (for progress states).
+ pub fn icon_animate(mut self, animate: bool) -> Self {
+ self.icon_animate = animate;
+ self
+ }
+
+ /// Sets the icon color (e.g., for warning/error states).
+ pub fn icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
+ self.icon_color = color.into();
+ self
+ }
+
+ /// Sets the tooltip text shown on hover.
+ pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
+ self.tooltip = Some(tooltip.into());
+ self
+ }
+
+ /// Shows a dismiss button on the right side.
+ pub fn with_dismiss(mut self) -> Self {
+ self.show_dismiss = true;
+ self
+ }
+
+ /// Sets the click handler for the main button area.
+ pub fn on_click(
+ mut self,
+ handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ ) -> Self {
+ self.on_click = Some(Box::new(handler));
+ self
+ }
+
+ /// Sets the click handler for the dismiss button.
+ pub fn on_dismiss(
+ mut self,
+ handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ ) -> Self {
+ self.on_dismiss = Some(Box::new(handler));
+ self
+ }
+
+ pub fn checking() -> Self {
+ Self::new(IconName::ArrowCircle, "Checking for Zed updates…").icon_animate(true)
+ }
+
+ pub fn downloading(version: impl Into<SharedString>) -> Self {
+ Self::new(IconName::Download, "Downloading Zed update…").tooltip(version)
+ }
+
+ pub fn installing(version: impl Into<SharedString>) -> Self {
+ Self::new(IconName::ArrowCircle, "Installing Zed update…")
+ .icon_animate(true)
+ .tooltip(version)
+ }
+
+ pub fn updated(version: impl Into<SharedString>) -> Self {
+ Self::new(IconName::Download, "Click to restart and update Zed")
+ .tooltip(version)
+ .with_dismiss()
+ }
+
+ pub fn errored(error: impl Into<SharedString>) -> Self {
+ Self::new(IconName::Warning, "Failed to update Zed")
+ .icon_color(Color::Warning)
+ .tooltip(error)
+ .with_dismiss()
+ }
+}
+
+impl RenderOnce for UpdateButton {
+ fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ let border_color = cx.theme().colors().border;
+
+ let icon = Icon::new(self.icon)
+ .size(IconSize::XSmall)
+ .when_some(self.icon_color, |this, color| this.color(color));
+ let icon_element = if self.icon_animate {
+ icon.with_rotate_animation(3).into_any_element()
+ } else {
+ icon.into_any_element()
+ };
+
+ let tooltip = self.tooltip.clone();
+
+ h_flex()
+ .mr_2()
+ .rounded_sm()
+ .border_1()
+ .border_color(border_color)
+ .child(
+ ButtonLike::new("update-button")
+ .child(
+ h_flex()
+ .h_full()
+ .gap_1()
+ .child(icon_element)
+ .child(Label::new(self.message).size(LabelSize::Small)),
+ )
+ .when_some(tooltip, |this, tooltip| {
+ this.tooltip(Tooltip::text(tooltip))
+ })
+ .when_some(self.on_click, |this, handler| this.on_click(handler)),
+ )
+ .when(self.show_dismiss, |this| {
+ this.child(
+ div().border_l_1().border_color(border_color).child(
+ IconButton::new("dismiss-update-button", IconName::Close)
+ .icon_size(IconSize::Indicator)
+ .when_some(self.on_dismiss, |this, handler| this.on_click(handler))
+ .tooltip(Tooltip::text("Dismiss")),
+ ),
+ )
+ })
+ }
+}
+
+impl Component for UpdateButton {
+ fn scope() -> ComponentScope {
+ ComponentScope::Collaboration
+ }
+
+ fn name() -> &'static str {
+ "UpdateButton"
+ }
+
+ fn description() -> Option<&'static str> {
+ Some(
+ "A button component displayed in the title bar to show auto-update status and allow users to restart Zed.",
+ )
+ }
+
+ fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+ let version = "1.99.0";
+
+ Some(
+ v_flex()
+ .gap_6()
+ .children(vec![
+ example_group_with_title(
+ "Progress States",
+ vec![
+ single_example("Checking", UpdateButton::checking().into_any_element()),
+ single_example(
+ "Downloading",
+ UpdateButton::downloading(version).into_any_element(),
+ ),
+ single_example(
+ "Installing",
+ UpdateButton::installing(version).into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "Actionable States",
+ vec![
+ single_example(
+ "Ready to Update",
+ UpdateButton::updated(version).into_any_element(),
+ ),
+ single_example(
+ "Error",
+ UpdateButton::errored("Network timeout").into_any_element(),
+ ),
+ ],
+ ),
+ ])
+ .into_any_element(),
+ )
+ }
+}