Part of #260: Help ▸ Open Log Folder shows the log file in the file manager
The desktop app keeps logs/lightcraft.log in its settings folder (#286) and a failed start names it (#370), but a user still had to find the folder by hand. The host now hands the UI its log file (Services .log_file) and Help ▸ Open Log Folder (app.openLogFolder) reveals it through the existing reveal service: Finder and Explorer select the file, Linux opens the folder. Off where no log is kept (the web, --memory, LIGHTCRAFT_NO_PREFS). Translated in de, es, ja, pt-br, ru, zh-hans and zh-hant; parity row MENU-HELP-LOGFOLDER; What's New and README note it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
bba4172f83
commit
0cf3ceb438
|
|
@ -359,7 +359,8 @@ Language**; the choice applies immediately and persists. See [docs/localization.
|
|||
**Logs:** the desktop app writes its log to standard error and to `logs/lightcraft.log` in its settings folder
|
||||
(Linux `$XDG_CONFIG_HOME/lightcraft/logs/`, by default `~/.config/lightcraft/logs/`; macOS
|
||||
`~/Library/Application Support/LightCraft/logs/`; Windows `%APPDATA%\LightCraft\logs\`), never in the library. A
|
||||
launch from a desktop menu or the Dock has no terminal, so attach this file to a bug report. Each start moves the
|
||||
launch from a desktop menu or the Dock has no terminal, so attach this file to a bug report (**Help ▸ Open Log
|
||||
Folder** shows it in the file manager). Each start moves the
|
||||
previous log to `lightcraft.1.log` and that one to `lightcraft.2.log`, so the log of a run that crashed survives the
|
||||
next start; the file stops growing at 16 MiB, `--version` and `--help` write none, and runs with
|
||||
`LIGHTCRAFT_NO_PREFS` log to standard error only. By default LightCraft's own crates log at `info` and everything else
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ fn services(originals: Originals, backend: Option<Backend>, files: Files, frozen
|
|||
lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(img), &lightcraft_codecs::EncodeMeta::default()).unwrap_or_default()
|
||||
})),
|
||||
reveal: None,
|
||||
log_file: None,
|
||||
open_with: None,
|
||||
open_url: Some(Box::new(|url: &str| {
|
||||
let w = web_sys::window().ok_or("no window")?;
|
||||
|
|
|
|||
|
|
@ -281,8 +281,9 @@ fn windows_open_url_command(url: &str) -> std::process::Command {
|
|||
c
|
||||
}
|
||||
|
||||
/// The platform services; `ctx` repaints when a file dialog closes.
|
||||
fn services(ctx: egui::Context) -> Services {
|
||||
/// The platform services; `ctx` repaints when a file dialog closes, and `log_file` is
|
||||
/// for Help ▸ Open Log Folder.
|
||||
fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services {
|
||||
Services {
|
||||
// Commands' file dialogs run on their own thread (#191): a dialog on the UI thread
|
||||
// stopped the window answering the compositor, which then reported the app as hung.
|
||||
|
|
@ -317,6 +318,7 @@ fn services(ctx: egui::Context) -> Services {
|
|||
.map_err(|e| format!("could not start the file dialog: {e}"))?;
|
||||
Ok(rx)
|
||||
})),
|
||||
log_file: log_file.map(|p| p.display().to_string()),
|
||||
pick_lightroom_catalog: Some(Box::new(|| {
|
||||
rfd::FileDialog::new()
|
||||
.set_title(lightcraft_ui_egui::i18n::tr("Import Lightroom Catalog"))
|
||||
|
|
@ -637,6 +639,8 @@ fn main() -> eframe::Result {
|
|||
wgpu_options: window_wgpu_options(),
|
||||
..Default::default()
|
||||
};
|
||||
// the app's copy: `log_file` is still named in the message of a start that fails
|
||||
let app_log_file = log_file.clone();
|
||||
let started = eframe::run_native(
|
||||
"LightCraft",
|
||||
options,
|
||||
|
|
@ -648,7 +652,7 @@ fn main() -> eframe::Result {
|
|||
std::env::var_os("LIGHTCRAFT_SAM3_DIR").map(std::path::PathBuf::from).or_else(|| config_dir().map(|d| d.join("models").join("sam3")));
|
||||
// the user's own download locations, one base URL per line (LIGHTCRAFT_SAM3_MIRRORS too)
|
||||
session.segmenter.mirrors_file = config_dir().map(|d| d.join("models").join("sam3-mirrors.txt"));
|
||||
let mut app = LightcraftApp::new(session, services(cc.egui_ctx.clone()));
|
||||
let mut app = LightcraftApp::new(session, services(cc.egui_ctx.clone(), app_log_file.as_deref()));
|
||||
if let Some(ui) = prefs {
|
||||
app.ui = ui;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1796,5 +1796,6 @@
|
|||
"Only": "Nur diese",
|
||||
"How to install the model": "So installierst du das Modell",
|
||||
"Install the SAM 3 Model": "SAM-3-Modell installieren",
|
||||
"This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "Diese Version kann das Modell noch nicht herunterladen. Zum Installieren von Hand lege model.safetensors, vocab.json und merges.txt aus Metas facebook/sam3 in den obigen Ordner: Objekt und Beschreiben funktionieren, sobald sie dort liegen."
|
||||
"This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "Diese Version kann das Modell noch nicht herunterladen. Zum Installieren von Hand lege model.safetensors, vocab.json und merges.txt aus Metas facebook/sam3 in den obigen Ordner: Objekt und Beschreiben funktionieren, sobald sie dort liegen.",
|
||||
"Open Log Folder": "Protokollordner öffnen"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1641,5 +1641,6 @@
|
|||
"Preview only: editing the camera's embedded JPEG": "Solo vista previa: editando el JPEG incrustado de la cámara",
|
||||
"Captured from": "Capturadas desde",
|
||||
"Captured until": "Capturadas hasta",
|
||||
"Only": "Solo"
|
||||
"Only": "Solo",
|
||||
"Open Log Folder": "Abrir carpeta de registros"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1641,5 +1641,6 @@
|
|||
"繁體中文(台灣)": "繁體中文(台灣)",
|
||||
"How to install the model": "モデルのインストール方法",
|
||||
"Install the SAM 3 Model": "SAM 3モデルのインストール",
|
||||
"This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "このビルドではまだモデルをダウンロードできません。手動でインストールするには、Meta の facebook/sam3 の model.safetensors、vocab.json、merges.txt を上のフォルダに置いてください。置いた時点で「オブジェクト」と「説明で選択」が使えるようになります。"
|
||||
"This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "このビルドではまだモデルをダウンロードできません。手動でインストールするには、Meta の facebook/sam3 の model.safetensors、vocab.json、merges.txt を上のフォルダに置いてください。置いた時点で「オブジェクト」と「説明で選択」が使えるようになります。",
|
||||
"Open Log Folder": "ログフォルダーを開く"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -941,6 +941,7 @@
|
|||
"Opacity (%)": "Opacidade (%)",
|
||||
"Open Library": "Abrir biblioteca",
|
||||
"Open Library…": "Abrir biblioteca…",
|
||||
"Open Log Folder": "Abrir pasta de logs",
|
||||
"Open in": "Abrir em",
|
||||
"Open in Detail": "Abrir em Detalhe",
|
||||
"Open the place in OpenStreetMap": "Abrir o local no OpenStreetMap",
|
||||
|
|
|
|||
|
|
@ -776,6 +776,7 @@
|
|||
"Opacity (%)": "Непрозрачность (%)",
|
||||
"Open Library": "Открыть библиотеку",
|
||||
"Open Library…": "Открыть библиотеку…",
|
||||
"Open Log Folder": "Открыть папку журналов",
|
||||
"Open in": "Открыть в",
|
||||
"Open in Detail": "Открыть в режиме «Детализация»",
|
||||
"Open the place in OpenStreetMap": "Открыть место в OpenStreetMap",
|
||||
|
|
|
|||
|
|
@ -1641,5 +1641,6 @@
|
|||
"That includes the photos in the folders inside it.": "其中文件夹里的照片也包括在内。",
|
||||
"How to install the model": "如何安装模型",
|
||||
"Install the SAM 3 Model": "安装 SAM 3 模型",
|
||||
"This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "此版本尚无法下载模型。如需手动安装,请将 Meta 的 facebook/sam3 中的 model.safetensors、vocab.json 和 merges.txt 放入上方的文件夹:放入后即可使用“对象”和“描述”。"
|
||||
"This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "此版本尚无法下载模型。如需手动安装,请将 Meta 的 facebook/sam3 中的 model.safetensors、vocab.json 和 merges.txt 放入上方的文件夹:放入后即可使用“对象”和“描述”。",
|
||||
"Open Log Folder": "打开日志文件夹"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1641,5 +1641,6 @@
|
|||
"That includes the photos in the folders inside it.": "其中資料夾裡的照片也包含在內。",
|
||||
"How to install the model": "如何安裝模型",
|
||||
"Install the SAM 3 Model": "安裝 SAM 3 模型",
|
||||
"This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "此版本尚無法下載模型。如需手動安裝,請將 Meta 的 facebook/sam3 中的 model.safetensors、vocab.json 和 merges.txt 放入上方的資料夾:放入後即可使用「物件」與「描述」。"
|
||||
"This build can't download the model yet. To install it by hand, put model.safetensors, vocab.json and merges.txt from Meta's facebook/sam3 in the folder above: Object and Describe work as soon as they are there.": "此版本尚無法下載模型。如需手動安裝,請將 Meta 的 facebook/sam3 中的 model.safetensors、vocab.json 和 merges.txt 放入上方的資料夾:放入後即可使用「物件」與「描述」。",
|
||||
"Open Log Folder": "開啟記錄資料夾"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@ pub struct Services {
|
|||
pub png: Option<PngEncode>,
|
||||
/// Show a file in the system file manager (desktop only).
|
||||
pub reveal: Option<RevealFn>,
|
||||
/// The host's current log file (`<settings>/logs/lightcraft.log`), which Help ▸ Open Log
|
||||
/// Folder reveals (#260). None where no log is kept: the web, `--memory`, `LIGHTCRAFT_NO_PREFS`.
|
||||
pub log_file: Option<String>,
|
||||
/// Choose a folder (Settings → General → Open Library…; desktop only).
|
||||
pub pick_folder: Option<PickFolder>,
|
||||
/// Open a Lightroom Classic `.lrcat` catalog for read-only import.
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ const LAYOUT: &[(&str, &[&str])] = &[
|
|||
"app.whatsNew",
|
||||
"app.shortcuts",
|
||||
"app.systemInfo",
|
||||
"app.openLogFolder",
|
||||
"---",
|
||||
"app.about",
|
||||
],
|
||||
|
|
@ -1086,6 +1087,37 @@ mod tests {
|
|||
assert!(run_item(&mut app, "merge.hdrLast", Value::Null).is_err());
|
||||
}
|
||||
|
||||
/// #260: Help ▸ Open Log Folder reveals the log file the host names. Without one (the web,
|
||||
/// `--memory`) or without a file manager to show it, the item is off and the command says why.
|
||||
#[test]
|
||||
fn open_log_folder_reveals_the_hosts_log_file() {
|
||||
let mut app = app();
|
||||
let help = |app: &LightcraftApp| menu_bar(app).into_iter().find(|(t, _)| t == "Help").map(|(_, items)| items).unwrap_or_default();
|
||||
assert!(matches!(find(&help(&app), "app.openLogFolder"), Some(MenuNode::Item { enabled: false, .. })), "listed in Help, off without a log");
|
||||
assert!(!crate::menus::ui_enabled(&app, "app.openLogFolder"));
|
||||
assert!(run_item(&mut app, "app.openLogFolder", Value::Null).is_err());
|
||||
let shown = std::sync::Arc::new(std::sync::Mutex::new(Vec::<String>::new()));
|
||||
let s = shown.clone();
|
||||
app.services.reveal = Some(Box::new(move |p: &str| {
|
||||
s.lock().unwrap().push(p.to_string());
|
||||
Ok(())
|
||||
}));
|
||||
// a file manager alone is not enough: this session keeps no log
|
||||
assert!(!crate::menus::ui_enabled(&app, "app.openLogFolder"));
|
||||
assert!(run_item(&mut app, "app.openLogFolder", Value::Null).is_err());
|
||||
assert!(shown.lock().unwrap().is_empty());
|
||||
let log = "/home/a/.config/lightcraft/logs/lightcraft.log";
|
||||
app.services.log_file = Some(log.into());
|
||||
assert!(crate::menus::ui_enabled(&app, "app.openLogFolder"));
|
||||
assert!(matches!(find(&help(&app), "app.openLogFolder"), Some(MenuNode::Item { enabled: true, .. })));
|
||||
let r = run_item(&mut app, "app.openLogFolder", Value::Null).unwrap();
|
||||
assert_eq!(r["path"], log);
|
||||
assert_eq!(shown.lock().unwrap().as_slice(), [log]);
|
||||
// the file manager's failure is the command's
|
||||
app.services.reveal = Some(Box::new(|_: &str| Err("no file manager".into())));
|
||||
assert_eq!(run_item(&mut app, "app.openLogFolder", Value::Null).unwrap_err(), "no file manager");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_missing_and_locate() {
|
||||
let dir = std::env::temp_dir().join(format!("lc-ui-missing-{}", std::process::id()));
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[
|
|||
("app.openLibrary", "Open Library…", None, "File"),
|
||||
("app.about", "About LightCraft", None, "Help"),
|
||||
("app.systemInfo", "System Info…", None, "Help"),
|
||||
("app.openLogFolder", "Open Log Folder", None, "Help"),
|
||||
("app.whatsNew", "What's New", None, "Help"),
|
||||
("dialog.cull", "Assisted Culling…", None, "Photo"),
|
||||
("app.help", "LightCraft Help", Some("F1"), "Help"),
|
||||
|
|
@ -1381,6 +1382,7 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option<Re
|
|||
}
|
||||
"app.export" => crate::control::export_active(app, p),
|
||||
"app.showInFinder" => show_in_finder(app),
|
||||
"app.openLogFolder" => open_log_folder(app),
|
||||
"app.discord" | "app.website" | "app.github" | "app.artcraft" | "app.help" | "app.feedback" => {
|
||||
let url = crate::links::url_of(id).unwrap_or(crate::links::WEBSITE);
|
||||
crate::links::open(app, url)
|
||||
|
|
@ -1417,6 +1419,7 @@ pub fn ui_enabled(app: &LightcraftApp, id: &str) -> bool {
|
|||
"view.fullScreenPreview" | "view.infoOverlay" | "view.navigator" => app.session.active().is_some() || app.ui.fullscreen,
|
||||
"app.openLibrary" | "file.addFolder" => app.services.pick_folder.is_some() && !crate::lightroom_import::is_running(app),
|
||||
"file.backupLibrary" => app.services.backup_library.is_some(),
|
||||
"app.openLogFolder" => app.services.reveal.is_some() && app.services.log_file.is_some(),
|
||||
"file.restoreLibrary" => app.services.restore_library.is_some(),
|
||||
"compare.swap" | "compare.makeSelect" => app.ui.view == ViewMode::Compare,
|
||||
s if s.starts_with("dialog.merge") || (s.starts_with("merge.") && s.ends_with("Last")) => {
|
||||
|
|
@ -1486,6 +1489,15 @@ pub fn reveal_label() -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
/// Help ▸ Open Log Folder: reveal the host's log file in the system file manager, so it can be
|
||||
/// attached to a report without hunting for the settings folder (#260).
|
||||
fn open_log_folder(app: &mut LightcraftApp) -> Result<Value, String> {
|
||||
let path = app.services.log_file.clone().ok_or("this session keeps no log file")?;
|
||||
let reveal = app.services.reveal.as_mut().ok_or("not available here")?;
|
||||
reveal(&path)?;
|
||||
Ok(json!({"path": path}))
|
||||
}
|
||||
|
||||
/// Reveal the active photo's original in the system file manager.
|
||||
fn show_in_finder(app: &mut LightcraftApp) -> Result<Value, String> {
|
||||
let id = app.session.active().ok_or("no photo selected")?;
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ and prints the summary; `cargo xtask parity --write` refreshes the summary table
|
|||
| V. Preferences (PREF) | 6 | 0 | 3 | 2 | 1/1 (100%) | 4/4 (100%) |
|
||||
| W. Cloud & AI infrastructure (CLOUD / AI) | 1 | 0 | 1 | 2 | — | — |
|
||||
| X. Cross-cutting behaviours (BEHAV) | 17 | 5 | 1 | 1 | 8/8 (100%) | 6/8 (75%) |
|
||||
| Y. Menus | 81 | 0 | 4 | 8 | 47/47 (100%) | 23/23 (100%) |
|
||||
| Y. Menus | 82 | 0 | 4 | 8 | 47/47 (100%) | 23/23 (100%) |
|
||||
| Z. Keyboard shortcuts (desktop) | 75 | 3 | 3 | 1 | 49/52 (94%) | 23/24 (96%) |
|
||||
| Lightroom Classic extras | 32 | 11 | 39 | 9 | — | 22/24 (92%) |
|
||||
| **Total** | 392 | 36 | 86 | 36 | 193/200 (97%) | 142/152 (93%) |
|
||||
| **Total** | 393 | 36 | 86 | 36 | 193/200 (97%) | 142/152 (93%) |
|
||||
|
||||
Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **79.8%** of 514 in-scope rows — P0 98.2% of 200 · P1 95.7% of 152 · P2 41.6% of 161.
|
||||
Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **79.8%** of 515 in-scope rows — P0 98.2% of 200 · P1 95.7% of 152 · P2 42.0% of 162.
|
||||
<!-- /parity:summary -->
|
||||
|
||||
## Top gaps
|
||||
|
|
@ -615,6 +615,7 @@ visible menu bar yet: the menu model is only exposed through the control channel
|
|||
| MENU-HELP-WHATSNEW | What's new | P2 | ✅ | `cmd:app.whatsNew` | |
|
||||
| MENU-HELP-SHORTCUTS | Keyboard shortcuts | P1 | ✅ | `cmd:app.shortcuts` | |
|
||||
| MENU-HELP-FEEDBACK | Send feedback | P2 | ✅ | `cmd:app.feedback` | opens a new issue on the project's GitHub |
|
||||
| MENU-HELP-LOGFOLDER | Open log folder | P2 | ✅ | `cmd:app.openLogFolder` | an extra (the reference app has no such item): Help ▸ Open Log Folder reveals `logs/lightcraft.log` in the system file manager (Finder, Explorer, or the folder on Linux); off on the web and in `--memory` sessions, which keep no log (issue #260) |
|
||||
| MENU-HELP-SYSINFO | System info | P2 | ✅ | `cmd:app.systemInfo`, `cmd:library.info` | Help ▸ System Info…: version, OS, CPU threads, GPU, memory budget, preview size, library, timings; Copy to Clipboard; JSON for agents (`open: false`) |
|
||||
| MENU-CTX-GRID | Photo context menu | P0 | ✅ | `crates/ui-egui/src/panels/grid.rs` (`context_menu`) | rate, flag, label, add to / remove from album, rename, virtual copy, version, stack, copy / paste / paste selected, reset, merge, rotate, show in Finder, export / export with preset, set as album cover (in an album), delete (restore / delete permanently in Recently Deleted) |
|
||||
| MENU-CTX-DETAIL | Loupe context menu | P1 | ✅ | `crates/ui-egui/src/panels/detail.rs` | Zoom submenu (fit, 100%, in, out), then the photo menu |
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@
|
|||
with the logs of the two previous runs beside it, so warnings and crashes of a run started from a desktop menu or the
|
||||
Dock can be attached to a bug report. `LIGHTCRAFT_LOG` works as before; `RUST_LOG` takes env_logger-style
|
||||
directives. See README → Quick start → Logs.
|
||||
- Help → Open Log Folder shows that log file in the file manager (Finder, Explorer, or the folder on Linux), so it
|
||||
can be attached to a report without hunting for the settings folder (issue #260).
|
||||
- `lightcraft-cli` logs warnings on stderr too (issue #168); `LIGHTCRAFT_LOG` or `RUST_LOG` picks another level.
|
||||
- LightCraft no longer crashes at launch on Windows PCs whose Vulkan driver is broken (issue #136, e.g. some Intel UHD
|
||||
630 drivers): on Windows the window and GPU rendering use DirectX 12 only and never load the Vulkan driver unless
|
||||
|
|
|
|||
Loading…
Reference in New Issue