auto_update_ui.rs

  1use auto_update::AutoUpdater;
  2use client::proto::UpdateNotification;
  3use editor::{Editor, MultiBuffer};
  4use gpui::{App, Context, DismissEvent, Entity, Window, actions, prelude::*};
  5use http_client::HttpClient;
  6use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
  7use release_channel::{AppVersion, ReleaseChannel};
  8use serde::Deserialize;
  9use smol::io::AsyncReadExt;
 10use util::ResultExt as _;
 11use workspace::Workspace;
 12use workspace::notifications::simple_message_notification::MessageNotification;
 13use workspace::notifications::{NotificationId, show_app_notification};
 14
 15actions!(
 16    auto_update,
 17    [
 18        /// Opens the release notes for the current version in a new tab.
 19        ViewReleaseNotesLocally
 20    ]
 21);
 22
 23pub fn init(cx: &mut App) {
 24    notify_if_app_was_updated(cx);
 25    cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
 26        workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, window, cx| {
 27            view_release_notes_locally(workspace, window, cx);
 28        });
 29    })
 30    .detach();
 31}
 32
 33#[derive(Deserialize)]
 34struct ReleaseNotesBody {
 35    title: String,
 36    release_notes: String,
 37}
 38
 39fn view_release_notes_locally(
 40    workspace: &mut Workspace,
 41    window: &mut Window,
 42    cx: &mut Context<Workspace>,
 43) {
 44    let release_channel = ReleaseChannel::global(cx);
 45
 46    let url = match release_channel {
 47        ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
 48        ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
 49        _ => None,
 50    };
 51
 52    if let Some(url) = url {
 53        cx.open_url(url);
 54        return;
 55    }
 56
 57    let version = AppVersion::global(cx).to_string();
 58
 59    let client = client::Client::global(cx).http_client();
 60    let url = client.build_url(&format!(
 61        "/api/release_notes/v2/{}/{}",
 62        release_channel.dev_name(),
 63        version
 64    ));
 65
 66    let markdown = workspace
 67        .app_state()
 68        .languages
 69        .language_for_name("Markdown");
 70
 71    workspace
 72        .with_local_workspace(window, cx, move |_, window, cx| {
 73            cx.spawn_in(window, async move |workspace, cx| {
 74                let markdown = markdown.await.log_err();
 75                let response = client.get(&url, Default::default(), true).await;
 76                let Some(mut response) = response.log_err() else {
 77                    return;
 78                };
 79
 80                let mut body = Vec::new();
 81                response.body_mut().read_to_end(&mut body).await.ok();
 82
 83                let body: serde_json::Result<ReleaseNotesBody> =
 84                    serde_json::from_slice(body.as_slice());
 85
 86                if let Ok(body) = body {
 87                    workspace
 88                        .update_in(cx, |workspace, window, cx| {
 89                            let project = workspace.project().clone();
 90                            let buffer = project.update(cx, |project, cx| {
 91                                project.create_local_buffer("", markdown, false, cx)
 92                            });
 93                            buffer.update(cx, |buffer, cx| {
 94                                buffer.edit([(0..0, body.release_notes)], None, cx)
 95                            });
 96                            let language_registry = project.read(cx).languages().clone();
 97
 98                            let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 99
100                            let editor = cx.new(|cx| {
101                                Editor::for_multibuffer(buffer, Some(project), window, cx)
102                            });
103                            let workspace_handle = workspace.weak_handle();
104                            let markdown_preview: Entity<MarkdownPreviewView> =
105                                MarkdownPreviewView::new(
106                                    MarkdownPreviewMode::Default,
107                                    editor,
108                                    workspace_handle,
109                                    language_registry,
110                                    window,
111                                    cx,
112                                );
113                            workspace.add_item_to_active_pane(
114                                Box::new(markdown_preview),
115                                None,
116                                true,
117                                window,
118                                cx,
119                            );
120                            cx.notify();
121                        })
122                        .log_err();
123                }
124            })
125            .detach();
126        })
127        .detach();
128}
129
130/// Shows a notification across all workspaces if an update was previously automatically installed
131/// and this notification had not yet been shown.
132pub fn notify_if_app_was_updated(cx: &mut App) {
133    let Some(updater) = AutoUpdater::get(cx) else {
134        return;
135    };
136
137    if let ReleaseChannel::Nightly = ReleaseChannel::global(cx) {
138        return;
139    }
140
141    let should_show_notification = updater.read(cx).should_show_update_notification(cx);
142    cx.spawn(async move |cx| {
143        let should_show_notification = should_show_notification.await?;
144        if should_show_notification {
145            cx.update(|cx| {
146                let version = updater.read(cx).current_version();
147                let app_name = ReleaseChannel::global(cx).display_name();
148                show_app_notification(
149                    NotificationId::unique::<UpdateNotification>(),
150                    cx,
151                    move |cx| {
152                        let workspace_handle = cx.entity().downgrade();
153                        cx.new(|cx| {
154                            MessageNotification::new(
155                                format!("Updated to {app_name} {}", version),
156                                cx,
157                            )
158                            .primary_message("View Release Notes")
159                            .primary_on_click(move |window, cx| {
160                                if let Some(workspace) = workspace_handle.upgrade() {
161                                    workspace.update(cx, |workspace, cx| {
162                                        crate::view_release_notes_locally(workspace, window, cx);
163                                    })
164                                }
165                                cx.emit(DismissEvent);
166                            })
167                            .show_suppress_button(false)
168                        })
169                    },
170                );
171                updater.update(cx, |updater, cx| {
172                    updater
173                        .set_should_show_update_notification(false, cx)
174                        .detach_and_log_err(cx);
175                })
176            })?;
177        }
178        anyhow::Ok(())
179    })
180    .detach();
181}