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