at main 206 lines 6.5 kB view raw
1//! Bug report dialog for the markdown editor. 2//! 3//! Captures editor state, DOM, and platform info for bug reports. 4//! All capture happens on-demand when the report button is clicked. 5 6use dioxus::prelude::*; 7 8#[allow(unused_imports)] 9use super::log_buffer; 10#[allow(unused_imports)] 11use super::storage::load_from_storage; 12 13/// Captured report data. 14#[derive(Clone, Default)] 15struct ReportData { 16 editor_text: String, 17 dom_html: String, 18 platform_info: String, 19 recent_logs: String, 20} 21 22impl ReportData { 23 /// Capture current state from DOM and LocalStorage. 24 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 25 fn capture(editor_id: &str) -> Self { 26 let dom_html = web_sys::window() 27 .and_then(|w| w.document()) 28 .and_then(|d| d.get_element_by_id(editor_id)) 29 .map(|e| e.outer_html()) 30 .unwrap_or_default(); 31 32 let editor_text = load_from_storage("current") 33 .map(|doc| doc.content()) 34 .unwrap_or_default(); 35 36 let platform_info = { 37 let plat = weaver_editor_browser::platform(); 38 format!( 39 "iOS: {}, Android: {}, Safari: {}, Chrome: {}, Firefox: {}, Mobile: {}\n\ 40 User Agent: {}", 41 plat.ios, 42 plat.android, 43 plat.safari, 44 plat.chrome, 45 plat.gecko, 46 plat.mobile, 47 web_sys::window() 48 .and_then(|w| w.navigator().user_agent().ok()) 49 .unwrap_or_default() 50 ) 51 }; 52 53 let recent_logs = log_buffer::get_logs(); 54 55 Self { 56 editor_text, 57 dom_html, 58 platform_info, 59 recent_logs, 60 } 61 } 62 63 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] 64 fn capture(_editor_id: &str) -> Self { 65 Self::default() 66 } 67 68 /// Generate mailto URL with report data. 69 fn to_mailto(&self, email: &str, comment: &str) -> String { 70 let subject = "Weaver Editor Bug Report"; 71 72 let body = format!( 73 "## Bug Report\n\n\ 74 ### Comment\n{}\n\n\ 75 ### Platform Info\n```\n{}\n```\n\n\ 76 ### Recent Logs\n```\n{}\n```\n\n\ 77 ### Editor Text\n```markdown\n{}\n```\n\n\ 78 ### DOM State\n```html\n{}\n```", 79 comment, self.platform_info, self.recent_logs, self.editor_text, self.dom_html 80 ); 81 82 let encoded_subject = urlencoding::encode(subject); 83 let encoded_body = urlencoding::encode(&body); 84 85 format!( 86 "mailto:{}?subject={}&body={}", 87 email, encoded_subject, encoded_body 88 ) 89 } 90} 91 92/// Props for the bug report button. 93#[derive(Props, Clone, PartialEq)] 94pub struct ReportButtonProps { 95 /// Email address to send reports to. 96 pub email: String, 97 /// Editor element ID for DOM capture. 98 pub editor_id: String, 99} 100 101/// Bug report button and dialog. 102#[component] 103pub fn ReportButton(props: ReportButtonProps) -> Element { 104 let mut show_dialog = use_signal(|| false); 105 let mut comment = use_signal(String::new); 106 let mut report_data = use_signal(ReportData::default); 107 108 let editor_id = props.editor_id.clone(); 109 let capture_state = move |_| { 110 report_data.set(ReportData::capture(&editor_id)); 111 show_dialog.set(true); 112 }; 113 114 let email = props.email.clone(); 115 let submit_report = move |_| { 116 let data = report_data(); 117 #[allow(unused_variables)] 118 let mailto_url = data.to_mailto(&email, &comment()); 119 120 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 121 if let Some(window) = web_sys::window() { 122 let _ = window.open_with_url(&mailto_url); 123 } 124 125 show_dialog.set(false); 126 comment.set(String::new()); 127 }; 128 129 let close_dialog = move |_| { 130 show_dialog.set(false); 131 }; 132 133 rsx! { 134 button { 135 class: "report-bug-button", 136 onclick: capture_state, 137 "Report Bug" 138 } 139 140 if show_dialog() { 141 div { 142 class: "report-dialog-overlay", 143 role: "dialog", 144 aria_modal: "true", 145 aria_labelledby: "report-dialog-title", 146 onclick: close_dialog, 147 148 div { 149 class: "report-dialog", 150 onclick: move |e| e.stop_propagation(), 151 152 h2 { id: "report-dialog-title", "Report a Bug" } 153 154 div { class: "report-section", 155 label { "Describe the issue:" } 156 textarea { 157 class: "report-comment", 158 aria_label: "Describe the issue", 159 placeholder: "What happened? What did you expect?", 160 value: "{comment}", 161 oninput: move |e| comment.set(e.value()), 162 rows: "4", 163 } 164 } 165 166 details { class: "report-details", 167 summary { "Captured Data (click to expand)" } 168 169 div { class: "report-section", 170 h4 { "Platform" } 171 pre { "{report_data().platform_info}" } 172 } 173 174 div { class: "report-section", 175 h4 { "Recent Logs" } 176 pre { "{report_data().recent_logs}" } 177 } 178 179 div { class: "report-section", 180 h4 { "Editor Text" } 181 pre { "{report_data().editor_text}" } 182 } 183 184 div { class: "report-section", 185 h4 { "DOM HTML" } 186 pre { "{report_data().dom_html}" } 187 } 188 } 189 190 div { class: "report-actions", 191 button { 192 class: "report-cancel", 193 onclick: close_dialog, 194 "Cancel" 195 } 196 button { 197 class: "report-submit", 198 onclick: submit_report, 199 "Open Email" 200 } 201 } 202 } 203 } 204 } 205 } 206}