diff --git a/crates/app/src/diagnostics_view.rs b/crates/app/src/diagnostics_view.rs index 363124a..cc4f227 100644 --- a/crates/app/src/diagnostics_view.rs +++ b/crates/app/src/diagnostics_view.rs @@ -18,13 +18,17 @@ use sync_core::store::SqliteInfo; pub struct DiagnosticsView { /// The model owns every read; the view renders what it last produced. model: Entity, + copied_notice: Option, } impl DiagnosticsView { /// A view over the diagnostics model. pub fn new(model: Entity, cx: &mut Context) -> Self { cx.observe(&model, |_, _, cx| cx.notify()).detach(); - Self { model } + Self { + model, + copied_notice: None, + } } /// The page header. @@ -421,6 +425,17 @@ impl DiagnosticsView { ) .into_any_element(), ); + if let Some(message) = &self.copied_notice { + rows.push( + notice( + "diagnostics-bundle-copy-notice", + message.clone(), + theme.info, + &palette, + ) + .into_any_element(), + ); + } rows.push( div() .id("diagnostics-bundle-copy-row") @@ -445,6 +460,29 @@ impl DiagnosticsView { ) .into_any_element(), ); + rows.push( + div() + .id("diagnostics-bundle-copy-folder-row") + .test_support() + .flex() + .flex_row() + .items_center() + .justify_between() + .gap_6() + .min_h(rems(2.25)) + .child(field_text( + "Copy folder", + "Puts the debug bundle folder on the clipboard, so it can be opened in a file manager.", + &palette, + )) + .child( + Button::new("diagnostics-bundle-copy-folder") + .label("Copy folder") + .accessibility_label("Copy the debug bundle folder path") + .on_click(cx.listener(|this, _, _, cx| this.copy_bundle_folder(cx))), + ) + .into_any_element(), + ); // A partial archive says what it could not hold, in the same // words its README uses. for (index, note) in report.notes.iter().enumerate() { @@ -486,6 +524,24 @@ impl DiagnosticsView { _ => return, }; cx.write_to_clipboard(ClipboardItem::new_string(path)); + self.copied_notice = Some("Debug bundle path copied to the clipboard.".to_string()); + cx.notify(); + } + + /// Puts the folder for the last archive on the clipboard. + fn copy_bundle_folder(&mut self, cx: &mut Context) { + let directory = match self.model.read(cx).bundle() { + BundleState::Ready(report) => { + report.path.parent().map(|path| path.display().to_string()) + } + _ => None, + }; + if let Some(directory) = directory { + cx.write_to_clipboard(ClipboardItem::new_string(directory)); + self.copied_notice = + Some("Debug bundle folder path copied to the clipboard.".to_string()); + cx.notify(); + } } /// The upload rate from the engine's meter, and the live progress. diff --git a/crates/app/tests/diagnostics.rs b/crates/app/tests/diagnostics.rs index 5aae93a..3f1b821 100644 --- a/crates/app/tests/diagnostics.rs +++ b/crates/app/tests/diagnostics.rs @@ -18,8 +18,8 @@ use std::time::Duration; use gpui_kit::test::TestWindowExt as _; use gpui_kit::{ - AnyWindowHandle, App, AppContext as _, Bounds, ElementId, Entity, Point, Role, TestAppContext, - Window, WindowBounds, WindowOptions, px, size, + AnyWindowHandle, App, AppContext as _, Bounds, ElementId, Entity, Point, Role, ScrollDelta, + TestAppContext, Window, WindowBounds, WindowOptions, point, px, size, }; use immich_sync::autostart::{Autostart, UnavailableAutostart}; use immich_sync::connection::ConnectionModel; @@ -1073,6 +1073,12 @@ async fn the_bundle_action_saves_an_archive_shows_its_path_and_copies_it(cx: &mu "the view shows the archive's full path" ); window.click("diagnostics-bundle-copy", cx); + let notice = window.find("diagnostics-bundle-copy-notice"); + assert_eq!(notice.role(), Some(Role::Alert)); + assert_eq!( + notice.label(), + Some("Debug bundle path copied to the clipboard.") + ); }) .expect("update the shell window"); let copied = cx @@ -1084,6 +1090,35 @@ async fn the_bundle_action_saves_an_archive_shows_its_path_and_copies_it(cx: &mu "the copy control puts the archive's path on the clipboard" ); + cx.update_window(window, |_, window, cx| { + window.scroll( + "diagnostics-view", + ScrollDelta::Pixels(point(px(0.), px(-1200.))), + cx, + ); + window.render_frame(cx); + window.click("diagnostics-bundle-copy-folder", cx); + let notice = window.find("diagnostics-bundle-copy-notice"); + assert_eq!( + notice.role(), + Some(Role::Alert), + "copy confirmation is announced once" + ); + assert_eq!( + notice.label(), + Some("Debug bundle folder path copied to the clipboard.") + ); + }) + .expect("update the shell window"); + let copied_folder = cx + .update(|cx| cx.read_from_clipboard().and_then(|item| item.text())) + .unwrap_or_default(); + assert_eq!( + copied_folder, + report.path.parent().unwrap().display().to_string(), + "the folder copy control puts the debug bundle folder on the clipboard" + ); + let (entries, bytes) = read_archive(&report.path); for required in [ "README.txt",