diff --git a/assets/locales/de.json b/assets/locales/de.json index 38d989e1..2407ac69 100644 --- a/assets/locales/de.json +++ b/assets/locales/de.json @@ -1,4 +1,9 @@ { + "Send your report on GitHub": "Bericht auf GitHub senden", + "1. Open GitHub draft below, then paste the report copied to your clipboard.": "1. Unten den GitHub-Entwurf öffnen und den kopierten Bericht aus der Zwischenablage einfügen.", + "1. Open GitHub draft below. Your title and report will be filled in.": "1. Unten den GitHub-Entwurf öffnen. Titel und Bericht werden vorausgefüllt.", + "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. Screenshot anhängen: kopieren und einfügen oder als PNG speichern und hochladen.", + "3. Click Submit new issue on GitHub to send your report.": "3. Auf GitHub auf Submit new issue klicken, um den Bericht zu senden.", "browser.body_name": "Körper {number}", "browser.component_name": "Komponente {number}", "browser.parameters": "Parameter ({count})", diff --git a/assets/locales/en.json b/assets/locales/en.json index 2474b6e6..f948a700 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -1,4 +1,9 @@ { + "Send your report on GitHub": "Send your report on GitHub", + "1. Open GitHub draft below, then paste the report copied to your clipboard.": "1. Open GitHub draft below, then paste the report copied to your clipboard.", + "1. Open GitHub draft below. Your title and report will be filled in.": "1. Open GitHub draft below. Your title and report will be filled in.", + "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. Attach your screenshot: copy and paste it, or save and upload the PNG.", + "3. Click Submit new issue on GitHub to send your report.": "3. Click Submit new issue on GitHub to send your report.", "browser.body_name": "Body{number}", "browser.component_name": "Component{number}", "browser.parameters": "Parameters ({count})", diff --git a/assets/locales/ja.json b/assets/locales/ja.json index 789688e8..0c491348 100644 --- a/assets/locales/ja.json +++ b/assets/locales/ja.json @@ -1,4 +1,9 @@ { + "Send your report on GitHub": "GitHubでレポートを送信", + "1. Open GitHub draft below, then paste the report copied to your clipboard.": "1. 下のボタンでGitHubの下書きを開き、クリップボードにコピーされたレポートを貼り付けます。", + "1. Open GitHub draft below. Your title and report will be filled in.": "1. 下のボタンでGitHubの下書きを開きます。タイトルとレポートは入力済みです。", + "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. スクリーンショットを添付します。コピーして貼り付けるか、PNGを保存してアップロードします。", + "3. Click Submit new issue on GitHub to send your report.": "3. GitHubでSubmit new issueをクリックしてレポートを送信します。", "browser.body_name": "ボディ{number}", "browser.component_name": "コンポーネント{number}", "browser.parameters": "パラメータ ({count})", diff --git a/assets/locales/ko.json b/assets/locales/ko.json index 4b31783d..8580a14d 100644 --- a/assets/locales/ko.json +++ b/assets/locales/ko.json @@ -1,4 +1,9 @@ { + "Send your report on GitHub": "GitHub에서 보고서 보내기", + "1. Open GitHub draft below, then paste the report copied to your clipboard.": "1. 아래에서 GitHub 초안을 열고 클립보드에 복사된 보고서를 붙여넣으세요.", + "1. Open GitHub draft below. Your title and report will be filled in.": "1. 아래에서 GitHub 초안을 여세요. 제목과 보고서가 자동으로 입력됩니다.", + "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. 스크린샷을 첨부하세요. 복사하여 붙여넣거나 PNG로 저장한 뒤 업로드하세요.", + "3. Click Submit new issue on GitHub to send your report.": "3. GitHub에서 Submit new issue를 클릭하여 보고서를 보내세요.", "browser.body_name": "바디{number}", "browser.component_name": "컴포넌트{number}", "browser.parameters": "매개변수 ({count})", diff --git a/assets/locales/zh.json b/assets/locales/zh.json index 422d1363..42727d85 100644 --- a/assets/locales/zh.json +++ b/assets/locales/zh.json @@ -1,4 +1,9 @@ { + "Send your report on GitHub": "在 GitHub 上发送报告", + "1. Open GitHub draft below, then paste the report copied to your clipboard.": "1. 点击下方按钮打开 GitHub 草稿,然后粘贴已复制到剪贴板的报告。", + "1. Open GitHub draft below. Your title and report will be filled in.": "1. 点击下方按钮打开 GitHub 草稿。标题和报告将自动填入。", + "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. 附上截图:复制并粘贴,或保存为 PNG 后上传。", + "3. Click Submit new issue on GitHub to send your report.": "3. 在 GitHub 上点击 Submit new issue 发送报告。", "browser.body_name": "实体{number}", "browser.component_name": "组件{number}", "browser.parameters": "参数 ({count})", diff --git a/crates/ui-egui/src/issue_report.rs b/crates/ui-egui/src/issue_report.rs index 614cd00d..9b3f4bc5 100644 --- a/crates/ui-egui/src/issue_report.rs +++ b/crates/ui-egui/src/issue_report.rs @@ -507,45 +507,53 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { let mut retake = false; let mut close = false; let screen = ctx.content_rect(); + let max_width = (screen.width() - 48.0).clamp(100.0, crate::frame::WINDOW_WIDE_MAX_W); crate::frame::window(ctx, crate::i18n::tr_label("Create Issue — review draft"), crate::frame::Width::Wide) .id(egui::Id::new("sc_issue_report")) .open(&mut open) .resizable(true) .default_width((screen.width() - 32.0).clamp(100.0, 640.0)) - .max_width((screen.width() - 24.0).max(100.0)) + .max_width(max_width) .max_height((screen.height() - 32.0).max(100.0)) - .vscroll(true) .show(ctx, |ui| { - ui.label(crate::i18n::tr_label("Review the report and screenshot before opening GitHub.")); - ui.add( - egui::TextEdit::singleline(&mut draft.title) - .id(egui::Id::new("issue_report_title")) - .hint_text(crate::i18n::tr_label("Issue title")) - .char_limit(160) - .desired_width(f32::INFINITY), - ); - egui::ScrollArea::vertical().id_salt("issue-report-body").max_height((screen.height() * 0.35).clamp(72.0, 240.0)).show(ui, |ui| { + // Only the editable report and evidence scroll. Keep the submission guide + // and actions visible even when a screenshot or long report needs more room. + egui::ScrollArea::vertical().id_salt("issue-report-review").max_height((screen.height() - 260.0).clamp(80.0, 520.0)).show(ui, |ui| { + ui.label(crate::i18n::tr_label("Review the report and screenshot before opening GitHub.")); + // TextEdit rounds its allocation to physical pixels. Leaving one pixel of + // room prevents that rounding from increasing the window width every frame + // at fractional display/UI scales (for example 140%). + let editor_width = |ui: &egui::Ui| (ui.available_width() - ui.ctx().pixels_per_point().recip()).max(24.0); ui.add( - egui::TextEdit::multiline(&mut draft.body) - .id(egui::Id::new("issue_report_body")) - .char_limit(MAX_BODY) - .desired_rows(5) - .desired_width(f32::INFINITY), + egui::TextEdit::singleline(&mut draft.title) + .id(egui::Id::new("issue_report_title")) + .hint_text(crate::i18n::tr_label("Issue title")) + .char_limit(160) + .desired_width(editor_width(ui)), ); + egui::ScrollArea::vertical().id_salt("issue-report-body").max_height((screen.height() * 0.35).clamp(72.0, 240.0)).show(ui, |ui| { + ui.add( + egui::TextEdit::multiline(&mut draft.body) + .id(egui::Id::new("issue_report_body")) + .char_limit(MAX_BODY) + .desired_rows(5) + .desired_width(editor_width(ui)), + ); + }); + truncate(&mut draft.title, 160); + truncate(&mut draft.body, MAX_BODY); + ui.label(RichText::new(crate::i18n::tr_label("Remove private design details or paths before sharing.")).small()); + ui.add(egui::Label::new(&draft.note).wrap()); + if let Some(image) = draft.image.as_ref() { + let texture = + draft.texture.get_or_insert_with(|| ctx.load_texture("issue-report-frame", image.clone(), egui::TextureOptions::LINEAR)); + ui.add( + egui::Image::new(&*texture) + .max_size(vec2(ui.available_width(), (screen.height() * 0.25).clamp(60.0, 220.0))) + .maintain_aspect_ratio(true), + ); + } }); - truncate(&mut draft.title, 160); - truncate(&mut draft.body, MAX_BODY); - ui.label(RichText::new(crate::i18n::tr_label("Remove private design details or paths before sharing.")).small()); - ui.label(&draft.note); - if let Some(image) = draft.image.as_ref() { - let texture = - draft.texture.get_or_insert_with(|| ctx.load_texture("issue-report-frame", image.clone(), egui::TextureOptions::LINEAR)); - ui.add( - egui::Image::new(&*texture) - .max_size(vec2(ui.available_width(), (screen.height() * 0.25).clamp(60.0, 220.0))) - .maintain_aspect_ratio(true), - ); - } ui.horizontal_wrapped(|ui| { retake = ui.button(crate::i18n::tr_label("Retake screenshot")).clicked(); if ui.add_enabled(draft.image.is_some(), egui::Button::new(crate::i18n::tr_label("Copy screenshot"))).clicked() @@ -575,24 +583,44 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { draft.note = "Screenshot removed from this draft.".into(); } }); + ui.separator(); + ui.label(RichText::new(crate::i18n::tr_label("Send your report on GitHub")).strong()); + let (url, copy_needed) = draft_url(&draft.title, &draft.body); + ui.add( + egui::Label::new(crate::i18n::tr_label(if copy_needed { + "1. Open GitHub draft below, then paste the report copied to your clipboard." + } else { + "1. Open GitHub draft below. Your title and report will be filled in." + })) + .wrap(), + ); + ui.add(egui::Label::new(crate::i18n::tr_label("2. Attach your screenshot: copy and paste it, or save and upload the PNG.")).wrap()); + ui.add(egui::Label::new(crate::i18n::tr_label("3. Click Submit new issue on GitHub to send your report.")).wrap()); ui.horizontal_wrapped(|ui| { - if ui.button(crate::i18n::tr_label("Copy report")).clicked() { - ctx.copy_text(draft.body.clone()); - draft.note = "Report copied. Paste it into the GitHub draft if needed.".into(); - } - let (url, copy_needed) = draft_url(&draft.title, &draft.body); - if ui.button(crate::i18n::tr_label("Open GitHub draft")).clicked() { + let t = crate::theme::Tokens::get(); + if ui + .add( + egui::Button::new(RichText::new(crate::i18n::tr_label("Open GitHub draft")).strong().color(t.text)) + .fill(t.accent_soft) + .stroke(egui::Stroke::new(1.0, t.accent)), + ) + .clicked() + { if copy_needed { ctx.copy_text(draft.body.clone()); } ctx.open_url(egui::OpenUrl::new_tab(url)); draft.note = if copy_needed { - "Report copied. Paste into the GitHub draft, then attach the screenshot." + "Report copied. Paste into the GitHub draft, attach the screenshot, then click Submit new issue on GitHub." } else { "Review the GitHub draft and attach the screenshot. GitHub's Submit new issue button publishes it." } .into(); } + if ui.button(crate::i18n::tr_label("Copy report")).clicked() { + ctx.copy_text(draft.body.clone()); + draft.note = "Report copied. Paste it into the GitHub draft if needed.".into(); + } close = ui.button(crate::i18n::tr_label("Close draft")).clicked(); }); }); @@ -743,6 +771,8 @@ mod tests { let reviewed = draft.body.clone(); h.app.issue_report.draft = Some(draft); h.frames(4); + assert!(h.widget("1. Open GitHub draft below, then paste the report copied to your clipboard.").is_some()); + assert!(h.widget("3. Click Submit new issue on GitHub to send your report.").is_some()); let [x, y] = h.widget("Open GitHub draft").unwrap(); assert_eq!(h.call("ui.move", json!({"x":x,"y":y}))["ok"], true); let (request, _reply) = crate::control::ControlRequest::new("ui.click", json!({"x":x,"y":y})); @@ -758,6 +788,35 @@ mod tests { ); assert_eq!(h.app.issue_report.draft.as_ref().unwrap().body, reviewed); assert!(h.app.issue_report.draft.as_ref().unwrap().note.starts_with("Report copied. Paste into the GitHub draft")); + assert!(h.app.issue_report.draft.as_ref().unwrap().note.contains("Submit new issue")); + } + + #[test] + fn issue_report_idle_window_does_not_grow_with_a_screenshot() { + for size in [[1600, 1000], [900, 700], [640, 480]] { + for scale in [1.0, 1.25, 1.4, 2.0] { + let mut h = crate::scenario::Harness::new(); + h.app.preferences.ui_scale = scale; + h.app.home.open = false; + h.call("ui.resize", json!({"width":size[0],"height":size[1]})); + let mut draft = draft_for(&h.app, None, None); + draft.note = "Screenshot captured locally. Review it before attaching it on GitHub.".into(); + draft.image = Some(Arc::new(ColorImage::new([1600, 1000], vec![egui::Color32::GREEN; 1_600_000]))); + h.app.issue_report.draft = Some(draft); + h.frames(20); + let rect = |h: &crate::scenario::Harness| egui::containers::AreaState::load(&h.ctx, egui::Id::new("sc_issue_report")).unwrap().rect(); + let settled = rect(&h); + h.frames(120); + let idle = rect(&h); + assert!((idle.width() - settled.width()).abs() < 1.0, "size={size:?}: {settled:?} grew to {idle:?}"); + assert!(idle.width() <= 790.0, "report must retain its width limit: {idle:?}"); + assert!(h.ctx.content_rect().contains_rect(idle), "size={size:?} scale={scale}: window must fit: {idle:?}"); + for label in ["Copy screenshot", "Save screenshot…", "Open GitHub draft", "Copy report", "Close draft"] { + let [x, y] = h.widget(label).unwrap(); + assert!(idle.contains(egui::pos2(x as f32, y as f32)), "{label} must stay visible: {idle:?}, {x}, {y}"); + } + } + } } #[test] diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 20a5b95d..a6ef1dae 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -126,7 +126,7 @@ unit regardless of the design, pass an expression with one: `"value": "50 mm"` o | `ui.file` | `path`; `insert?`: bool | starts an interactive background Open or Insert; poll `ui.inspect.file_busy` for completion; the window stays responsive | | `ui.home` | `open?`: bool; `sample?`: index | the start page: show or hide it, open a built-in sample (once built); returns `open` and the recent designs | | `ui.shortcut` | `command`, `key?`, `replace?` | a command's key, or rebinds it (`""` clears; a key another command uses is refused unless `replace`) | -| `ui.help` | `item?`: `about`, `shortcuts` (read only), `report` (copies diagnostics), `crashes` (the Crash Reports window) | the version, commit, build date and diagnostic text | +| `ui.help` | `item?`: `about`, `shortcuts` (read only), `report` (opens a report draft for review), `crashes` (the Crash Reports window) | the version, commit, build date and diagnostic text | | `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`, `openscad`), `zoom_reverse`, `zoom_to_cursor`, `smooth_wheel_zoom` (default true), `trackpad_pan` (default true: two-finger scroll pans, pinch zooms), `orbit_cursor`, `look_at_anim`, `look_at_speed`, `middle_double_click` (`home` (default), `fit`, `off`), `msaa`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `language` (`en`, `ja`, `de`, `ko`, `ru`, `zh`; see [localization](localization.md)), `ui_scale`, `up_axis` (`z`, `y`), `offer_stand_up`, `check_updates` (default true; [updates](book/src/guide/updates.md)); `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` | | `ui.capture` | `action?`: `capture`, `revert`, `cancel` | answers the Capture Position question (asked when a command starts or the design is saved while components were moved without capturing); without `action`, what waits | | `ui.window` | `action`: minimize, maximize, restore, toggle, close | what the title bar's caption buttons do (on Windows and Linux the application bar is the title bar); close asks about unsaved designs first | diff --git a/docs/crash-reports.md b/docs/crash-reports.md index 76e85c45..dae81917 100644 --- a/docs/crash-reports.md +++ b/docs/crash-reports.md @@ -6,6 +6,14 @@ and the exact commands that trigger it. Reports are never sent anywhere. To shar **Report…** in Help ▸ Crash Reports…, which opens a GitHub issue draft. Nothing is shared until you review and submit it, and the design is not part of the draft. +To send a report: + +1. Review the title, report text and screenshot in SolidCraft, then click **Open GitHub draft**. + For long reports, SolidCraft copies the text to your clipboard; paste it into the GitHub draft. +2. Attach the screenshot if you want to share it: use **Copy screenshot** and paste it into + GitHub, or use **Save screenshot…** and upload the PNG. Screenshots are not attached automatically. +3. Click **Submit new issue** on GitHub to publish the report. Opening the draft alone does not send it. + ## Where they are `/solidcraft/crashes/`, next to the recovery folder: