auto_update_ui.rs

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