mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
docs/parity-checklist.md: header and revision rows from both sides; generated summary line left for cargo xtask parity --write
This commit is contained in:
@@ -1777,6 +1777,7 @@
|
||||
"Show in Explorer": "Zobrazit v Průzkumníku",
|
||||
"Show in File Manager": "Zobrazit ve správci souborů",
|
||||
"Show in Finder": "Zobrazit ve Finderu",
|
||||
"Show in Folders": "Zobrazit ve složkách",
|
||||
"Show less": "Zobrazit méně",
|
||||
"Show on Map": "Zobrazit na mapě",
|
||||
"Show overlay (O)": "Zobrazit překrytí (O)",
|
||||
|
||||
@@ -1428,6 +1428,7 @@
|
||||
"Show in Explorer": "Im Explorer anzeigen",
|
||||
"Show in File Manager": "Im Dateimanager anzeigen",
|
||||
"Show in Finder": "Im Finder anzeigen",
|
||||
"Show in Folders": "In Ordnern anzeigen",
|
||||
"Show on Map": "Auf Karte anzeigen",
|
||||
"Show overlay (O)": "Überlagerung anzeigen (O)",
|
||||
"Show ratings, flags and edit badges": "Bewertungen, Markierungen und Bearbeitungssymbole anzeigen",
|
||||
|
||||
@@ -1441,6 +1441,7 @@
|
||||
"Show in Explorer": "Mostrar en el Explorador",
|
||||
"Show in File Manager": "Mostrar en el gestor de archivos",
|
||||
"Show in Finder": "Mostrar en el Finder",
|
||||
"Show in Folders": "Mostrar en Carpetas",
|
||||
"Show on Map": "Mostrar en el mapa",
|
||||
"Show overlay (O)": "Mostrar superposición (O)",
|
||||
"Show ratings, flags and edit badges": "Mostrar valoraciones, marcas e indicadores de edición",
|
||||
|
||||
@@ -1364,6 +1364,7 @@
|
||||
"Show in Explorer": "Afficher dans l'Explorateur",
|
||||
"Show in File Manager": "Afficher dans le gestionnaire de fichiers",
|
||||
"Show in Finder": "Afficher dans le Finder",
|
||||
"Show in Folders": "Afficher dans les dossiers",
|
||||
"Show on Map": "Afficher sur la carte",
|
||||
"Show overlay (O)": "Afficher l'incrustation (O)",
|
||||
"Show ratings, flags and edit badges": "Afficher les notes, les drapeaux et les pastilles de retouche",
|
||||
|
||||
@@ -1402,6 +1402,7 @@
|
||||
"Show in Explorer": "Mostra in Esplora file",
|
||||
"Show in File Manager": "Mostra nel gestore file",
|
||||
"Show in Finder": "Mostra nel Finder",
|
||||
"Show in Folders": "Mostra in Cartelle",
|
||||
"Show on Map": "Mostra sulla mappa",
|
||||
"Show overlay (O)": "Mostra sovrapposizione (O)",
|
||||
"Show ratings, flags and edit badges": "Mostra valutazioni, contrassegni e indicatori di modifica",
|
||||
|
||||
@@ -1310,6 +1310,7 @@
|
||||
"Show in Explorer": "エクスプローラーで表示",
|
||||
"Show in File Manager": "ファイルマネージャーで表示",
|
||||
"Show in Finder": "Finderで表示",
|
||||
"Show in Folders": "フォルダで表示",
|
||||
"Show on Map": "地図で表示",
|
||||
"Show overlay (O)": "重ね表示(O)",
|
||||
"Show ratings, flags and edit badges": "評価・フラグ・編集済みマークを表示",
|
||||
|
||||
@@ -1927,6 +1927,7 @@
|
||||
"Show in Explorer": "Explorer-ში ჩვენება",
|
||||
"Show in File Manager": "ფაილების მენეჯერში ჩვენება",
|
||||
"Show in Finder": "Finder-ში ჩვენება",
|
||||
"Show in Folders": "საქაღალდეებში ჩვენება",
|
||||
"Show less": "ნაკლების ჩვენება",
|
||||
"Show on Map": "რუკაზე ჩვენება",
|
||||
"Show overlay (O)": "ზედდების ჩვენება (O)",
|
||||
|
||||
@@ -1338,6 +1338,7 @@
|
||||
"Show in Explorer": "Pokaż w Eksploratorze",
|
||||
"Show in File Manager": "Pokaż w menedżerze plików",
|
||||
"Show in Finder": "Pokaż w Finderze",
|
||||
"Show in Folders": "Pokaż w folderach",
|
||||
"Show on Map": "Pokaż na mapie",
|
||||
"Show overlay (O)": "Pokaż nakładkę (O)",
|
||||
"Show ratings, flags and edit badges": "Pokaż oceny, flagi i znaczniki edycji",
|
||||
|
||||
@@ -1387,6 +1387,7 @@
|
||||
"Show in Explorer": "Mostrar no Explorador de Arquivos",
|
||||
"Show in File Manager": "Mostrar no gerenciador de arquivos",
|
||||
"Show in Finder": "Mostrar no Finder",
|
||||
"Show in Folders": "Mostrar em Pastas",
|
||||
"Show on Map": "Mostrar no mapa",
|
||||
"Show overlay (O)": "Mostrar sobreposição (O)",
|
||||
"Show ratings, flags and edit badges": "Mostrar avaliações, marcações e indicadores de edição",
|
||||
|
||||
@@ -1155,6 +1155,7 @@
|
||||
"Show in Explorer": "Показать в «Проводнике»",
|
||||
"Show in File Manager": "Показать в файловом менеджере",
|
||||
"Show in Finder": "Показать в Finder",
|
||||
"Show in Folders": "Показать в папках",
|
||||
"Show on Map": "Показать на карте",
|
||||
"Show overlay (O)": "Показать наложение (O)",
|
||||
"Show ratings, flags and edit badges": "Показывать оценки, флаги и значки правок",
|
||||
|
||||
@@ -1379,6 +1379,7 @@
|
||||
"Show in Explorer": "Показати в Провіднику",
|
||||
"Show in File Manager": "Показати у файловому менеджері",
|
||||
"Show in Finder": "Показати у Finder",
|
||||
"Show in Folders": "Показати в теках",
|
||||
"Show on Map": "Показати на карті",
|
||||
"Show overlay (O)": "Показати накладання (O)",
|
||||
"Show ratings, flags and edit badges": "Показати оцінки, прапорці й позначки змін",
|
||||
|
||||
@@ -1372,6 +1372,7 @@
|
||||
"Show in Explorer": "在文件资源管理器中显示",
|
||||
"Show in File Manager": "在文件管理器中显示",
|
||||
"Show in Finder": "在 Finder 中显示",
|
||||
"Show in Folders": "在文件夹中显示",
|
||||
"Show on Map": "在地图上显示",
|
||||
"Show overlay (O)": "显示叠加 (O)",
|
||||
"Show ratings, flags and edit badges": "显示星级、旗标和编辑标记",
|
||||
|
||||
@@ -1372,6 +1372,7 @@
|
||||
"Show in Explorer": "在檔案總管中顯示",
|
||||
"Show in File Manager": "在檔案管理員中顯示",
|
||||
"Show in Finder": "在 Finder 中顯示",
|
||||
"Show in Folders": "在資料夾中顯示",
|
||||
"Show on Map": "在地圖上顯示",
|
||||
"Show overlay (O)": "顯示疊加(O)",
|
||||
"Show ratings, flags and edit badges": "顯示評分、旗標與編輯標記",
|
||||
|
||||
@@ -334,6 +334,9 @@ pub(crate) fn exposed(app: &crate::LightcraftApp, panel: Panel) -> bool {
|
||||
pub(crate) fn reveal_command(app: &mut crate::LightcraftApp, id: &str) -> Result<(), String> {
|
||||
let panel = if id == "panel.presets" && app.ui.presets {
|
||||
Some(Panel::Presets)
|
||||
} else if id == "app.showInFolders" {
|
||||
// the sidebar it scrolls, also when it is a tab behind another panel
|
||||
Some(Panel::Library)
|
||||
} else if (id.starts_with("panel.") && id != "panel.close" && id != "panel.presets"
|
||||
|| id.starts_with("section.")
|
||||
|| id.starts_with("tool.") && id != "tool.none")
|
||||
|
||||
@@ -1759,6 +1759,10 @@ impl Caches {
|
||||
self.folder_tree = Some((cat.revision, t.clone()));
|
||||
t
|
||||
}
|
||||
/// The folder tree as last built, when it still holds (a read-only check cannot rebuild it).
|
||||
pub(crate) fn folder_tree_if_fresh(&self, cat: &lightcraft_catalog::Catalog) -> Option<std::sync::Arc<Vec<lightcraft_catalog::FolderNode>>> {
|
||||
self.folder_tree.as_ref().filter(|(r, _)| still_right(cat, *r, READS_FILE)).map(|(_, t)| t.clone())
|
||||
}
|
||||
/// The library's keyword tree.
|
||||
pub fn keyword_tree(&mut self, cat: &lightcraft_catalog::Catalog) -> std::sync::Arc<Vec<lightcraft_catalog::KeywordNode>> {
|
||||
let kept = self.keyword_tree.take().filter(|(r, _)| still_right(cat, *r, READS_META)).map(|(_, t)| t);
|
||||
|
||||
@@ -214,6 +214,7 @@ const LAYOUT: &[(&str, &[&str])] = &[
|
||||
"photo.reload",
|
||||
"photo.readFocusPoints",
|
||||
"app.showInFinder",
|
||||
"app.showInFolders",
|
||||
"dialog.rename",
|
||||
"dialog.captureTime",
|
||||
"photo.tagFromTracklog",
|
||||
|
||||
@@ -258,6 +258,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[
|
||||
("app.resetShortcuts", "Reset All Keyboard Shortcuts", None, ""),
|
||||
("app.export", "Export Now", None, ""),
|
||||
("app.showInFinder", "Show in Finder", Some("Cmd+R"), "Photo"),
|
||||
("app.showInFolders", "Show in Folders", None, "Photo"),
|
||||
("app.openSamModelFolder", "Show in File Manager", None, ""),
|
||||
("dialog.rename", "Rename Photos…", Some("F2"), "Photo"),
|
||||
("dialog.labelNames", "Edit Color Label Names…", None, ""),
|
||||
@@ -2149,6 +2150,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.showInFolders" => show_in_folders(app, p.get("id").and_then(Value::as_u64).map(lightcraft_catalog::PhotoId)),
|
||||
"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);
|
||||
@@ -2203,6 +2205,7 @@ pub fn ui_enabled(app: &LightcraftApp, id: &str) -> bool {
|
||||
.and_then(|id| app.session.catalog.photo(id))
|
||||
.is_some_and(|p| matches!(p.source, lightcraft_engine::catalog::Source::File { .. }))
|
||||
}
|
||||
"app.showInFolders" => show_in_folders_enabled(app, None),
|
||||
"app.openSamModelFolder" => app.session.segmenter.dir.is_some() && app.services.open_with.is_some(),
|
||||
"file.exportPresets" => app.session.presets.iter().any(|p| !p.builtin),
|
||||
"file.exportMaskEffects" => !app.session.mask_effects.is_empty(),
|
||||
@@ -2332,6 +2335,39 @@ fn show_in_finder(app: &mut LightcraftApp) -> Result<Value, String> {
|
||||
Ok(json!({"path": path}))
|
||||
}
|
||||
|
||||
/// The folder the active photo was imported from, as the sidebar's Folders section counts it (a
|
||||
/// photo straight in a disk's root is in the root). `None` for a demo scene, a photo only browsed
|
||||
/// in Local or in Recently Deleted, and one on the startup disk's root, which has no row to show.
|
||||
fn photo_folder(app: &LightcraftApp, id: Option<lightcraft_catalog::PhotoId>) -> Option<String> {
|
||||
let photo = id.or_else(|| app.session.active()).and_then(|id| app.session.catalog.photo(id)).filter(|p| p.in_library())?;
|
||||
let lightcraft_engine::catalog::Source::File { path } = &photo.source else { return None };
|
||||
let dir = path.rfind(['/', '\\']).and_then(|i| path.get(..i.max(1)))?;
|
||||
(!lightcraft_catalog::folders::is_startup_disk(dir)).then(|| dir.to_string())
|
||||
}
|
||||
|
||||
/// Whether Show in Folders has a row to show for photo `id` (the active one when `None`): its
|
||||
/// folder must be a row of the tree, which stops `MAX_DEPTH` levels down. With no fresh tree to
|
||||
/// look in, the folder alone decides, and the command still checks.
|
||||
pub fn show_in_folders_enabled(app: &LightcraftApp, id: Option<lightcraft_catalog::PhotoId>) -> bool {
|
||||
let Some(dir) = photo_folder(app, id) else { return false };
|
||||
app.caches.folder_tree_if_fresh(&app.session.catalog).is_none_or(|tree| crate::panels::left::folder_row_of(&tree, &dir).is_some())
|
||||
}
|
||||
|
||||
/// Photo ▸ Show in Folders (Lightroom's Go to Folder in Library): show the folder of photo `id`
|
||||
/// (the active one when `None`; the grid's menu passes the photo right-clicked) as a click on its
|
||||
/// row in the sidebar's Folders section does, and bring the row into view: the sidebar, the
|
||||
/// section and the folders above it open, and it scrolls there (`reveal_folder`).
|
||||
fn show_in_folders(app: &mut LightcraftApp, id: Option<lightcraft_catalog::PhotoId>) -> Result<Value, String> {
|
||||
let dir = photo_folder(app, id).ok_or("this photo is not in a folder of the library")?;
|
||||
let tree = app.caches.folder_tree(&app.session.catalog);
|
||||
// the row's own spelling (forward slashes): what its click hands `library.source`, and its id
|
||||
let path = crate::panels::left::folder_row_of(&tree, &dir).map(|n| n.path.clone()).ok_or("this photo's folder is not in Folders")?;
|
||||
app.run("library.source", json!({"kind": "libraryFolder", "path": path}))?;
|
||||
app.ui.left_panel = true;
|
||||
app.ui.reveal_folder = Some(path.clone());
|
||||
Ok(json!({"path": path}))
|
||||
}
|
||||
|
||||
/// Photo ▸ Edit In: render a copy of each selected photo with its edits (stacked on its original)
|
||||
/// as Settings ▸ External Editing asks for that editor, then open them there. `p` may override the
|
||||
/// file (`format`, `colorSpace`, `bitDepth`) and the editor (`app`). PhotoCraft is checked first,
|
||||
|
||||
@@ -1404,6 +1404,11 @@ pub fn context_menu(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) {
|
||||
if ui.add_enabled(crate::menus::ui_enabled(app, "app.showInFinder"), egui::Button::new(crate::i18n::tr(crate::menus::reveal_label()))).clicked() {
|
||||
let _ = app.run("app.showInFinder", json!({}));
|
||||
}
|
||||
// the photo's folder in the sidebar's Folders section, as Lightroom's Go to Folder in Library
|
||||
// (the photo right-clicked, which a multi-selection may leave other than the active one)
|
||||
if ui.add_enabled(crate::menus::show_in_folders_enabled(app, Some(id)), egui::Button::new(crate::i18n::tr("Show in Folders"))).clicked() {
|
||||
let _ = app.run("app.showInFolders", json!({"id": id.0}));
|
||||
}
|
||||
if ui.button(crate::i18n::tr("Export…")).clicked() {
|
||||
let _ = app.run("dialog.export", json!({}));
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ const ROW_HEIGHT: f32 = 29.0;
|
||||
/// this frame: set before the sections are drawn, cleared after.
|
||||
const SCROLL_TO_ROW: &str = "left-scroll-to-row";
|
||||
|
||||
/// Temp-data key of the Folders choice whose rows above were last opened ([`reveal_chosen`]):
|
||||
/// removed, they open again.
|
||||
const LIBFOLDER_REVEALED: &str = "libfolder-revealed";
|
||||
|
||||
/// A row says how wide it needs to be.
|
||||
fn note_width(ui: &egui::Ui, w: f32) {
|
||||
ui.data_mut(|d| {
|
||||
@@ -720,6 +724,14 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
// …and the sidebar scrolls to it
|
||||
ui.data_mut(|d| d.insert_temp(egui::Id::new(SCROLL_TO_ROW), format!("keyword:{path}").to_lowercase()));
|
||||
}
|
||||
// the folder Show in Folders went to: Folders is shown and opened, and so are the
|
||||
// folders above it, also when it was already the one shown and one was folded by hand…
|
||||
if let Some(path) = app.ui.reveal_folder.take() {
|
||||
app.ui.sidebar.reveal(SidebarSection::Folders);
|
||||
ui.data_mut(|d| d.remove::<Option<String>>(egui::Id::new(LIBFOLDER_REVEALED)));
|
||||
// …and the sidebar scrolls to it
|
||||
ui.data_mut(|d| d.insert_temp(egui::Id::new(SCROLL_TO_ROW), format!("libfolder:{path}").to_lowercase()));
|
||||
}
|
||||
// Local keeps track of the folder being browsed whether or not it is drawn
|
||||
let local = local_now(app, ui);
|
||||
let shown: Vec<SidebarSection> = app.ui.sidebar.shown().collect();
|
||||
@@ -1800,7 +1812,7 @@ fn is_within(app: &LightcraftApp, id: lightcraft_catalog::AlbumId, ancestor: lig
|
||||
fn reveal_chosen(app: &LightcraftApp, ui: &egui::Ui, tree: &[FolderNode]) {
|
||||
let shown = app.session.library_folder.clone().filter(|_| app.session.source == LibrarySource::LibraryFolder);
|
||||
let chosen = shown.filter(|c| !lightcraft_catalog::query::folder_key(c).is_empty());
|
||||
let seen = egui::Id::new("libfolder-revealed");
|
||||
let seen = egui::Id::new(LIBFOLDER_REVEALED);
|
||||
let now = chosen.as_deref().map(lightcraft_catalog::query::folder_key);
|
||||
if ui.data(|d| d.get_temp::<Option<String>>(seen)) == Some(now.clone()) {
|
||||
return;
|
||||
@@ -1819,6 +1831,16 @@ fn reveal_chosen(app: &LightcraftApp, ui: &egui::Ui, tree: &[FolderNode]) {
|
||||
open_above(ui, tree, &chosen);
|
||||
}
|
||||
|
||||
/// The Folders row that stands for folder `dir`, however it is spelled. `None` when no row is that
|
||||
/// folder.
|
||||
pub(crate) fn folder_row_of<'a>(tree: &'a [FolderNode], dir: &str) -> Option<&'a FolderNode> {
|
||||
// only the rows that hold `dir` are opened (the startup disk's holds the other disks' paths
|
||||
// too, so more than one may), and the tree is at most `MAX_DEPTH` deep
|
||||
tree.iter()
|
||||
.filter(|n| lightcraft_catalog::query::folder_within(dir, &n.path))
|
||||
.find_map(|n| if same_folder(&n.path, dir) { Some(n) } else { folder_row_of(&n.children, dir) })
|
||||
}
|
||||
|
||||
fn folder_rows(app: &mut LightcraftApp, ui: &mut egui::Ui, nodes: &[FolderNode], indent: f32) {
|
||||
for n in nodes {
|
||||
let key = lightcraft_catalog::query::folder_key(&n.path);
|
||||
|
||||
@@ -870,6 +870,10 @@ pub struct UiState {
|
||||
/// A keyword just made from the sidebar: Keywords opens down to it, once.
|
||||
#[serde(skip)]
|
||||
pub reveal_keyword: Option<String>,
|
||||
/// A folder Photo ▸ Show in Folders went to (its Folders row's path): the sidebar's Folders
|
||||
/// section opens down to it and scrolls to it, once.
|
||||
#[serde(skip)]
|
||||
pub reveal_folder: Option<String>,
|
||||
/// An album or folder row being dragged in the sidebar (dropped on a folder to move it there).
|
||||
#[serde(skip)]
|
||||
pub dragging_album: Option<u64>,
|
||||
@@ -1422,6 +1426,7 @@ impl Default for UiState {
|
||||
dragging_photos: None,
|
||||
reveal_album: None,
|
||||
reveal_keyword: None,
|
||||
reveal_folder: None,
|
||||
dragging_album: None,
|
||||
dragging_section: None,
|
||||
curve_channel: "parametric".into(),
|
||||
|
||||
@@ -1652,6 +1652,103 @@ fn the_chosen_folder_is_always_in_view() {
|
||||
assert!(!has(&h, "source:libfolder:/pics/trip/day1"));
|
||||
}
|
||||
|
||||
/// Make the photo imported from `path` the active one.
|
||||
fn activate(h: &mut Headless, path: &str) {
|
||||
let id = h.app.session.catalog.photos().find(|p| matches!(&p.source, lightcraft_catalog::Source::File { path: f } if f == path)).unwrap().id;
|
||||
h.app.run("library.select", json!({"ids": [id.0], "active": id.0})).unwrap();
|
||||
h.step();
|
||||
}
|
||||
|
||||
/// Photo ▸ Show in Folders (Lightroom's Go to Folder in Library) shows the active photo's folder
|
||||
/// as a click on its Folders row does: the sidebar, the section and the folders above the row open,
|
||||
/// also when the folder was already shown and a row above it was folded by hand.
|
||||
#[test]
|
||||
fn show_in_folders_chooses_the_photos_folder_and_opens_the_rows_above_it() {
|
||||
use crate::sidebar::SidebarSection;
|
||||
let mut h = folders_app(&["/pics/trip/day1/a.jpg", "/pics/trip/b.jpg", "/pics/home/c.jpg"]);
|
||||
activate(&mut h, "/pics/trip/day1/a.jpg");
|
||||
assert!(crate::menus::ui_enabled(&h.app, "app.showInFolders"));
|
||||
click(&mut h, "sidebarSection:folders");
|
||||
h.app.run("view.leftPanel", json!({})).unwrap();
|
||||
h.step();
|
||||
assert!(!h.app.ui.left_panel && !has(&h, "sidebarTitle"), "the sidebar is hidden");
|
||||
let r = h.request("ui.menu.invoke", json!({"id": "app.showInFolders"}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.step();
|
||||
h.step();
|
||||
assert_eq!(h.app.session.source, lightcraft_engine::LibrarySource::LibraryFolder);
|
||||
assert_eq!(h.app.session.library_folder.as_deref(), Some("/pics/trip/day1"));
|
||||
assert_eq!(h.app.session.visible().len(), 1, "the folder's photos fill the grid");
|
||||
assert_eq!(h.app.session.active(), h.app.session.visible().first().copied(), "the photo stays the active one");
|
||||
assert!(h.app.ui.left_panel && !h.app.ui.sidebar.is_collapsed(SidebarSection::Folders), "the sidebar and Folders opened");
|
||||
assert!(has(&h, "source:libfolder:/pics/trip/day1") && has(&h, "highlight:libfolder:/pics/trip/day1"), "its row is shown chosen");
|
||||
// folded by hand, then asked again: the rows above open again
|
||||
click(&mut h, "libraryFolderToggle:/pics/trip");
|
||||
assert!(!has(&h, "source:libfolder:/pics/trip/day1"));
|
||||
h.app.run("app.showInFolders", json!({})).unwrap();
|
||||
h.step();
|
||||
h.step();
|
||||
assert!(has(&h, "source:libfolder:/pics/trip/day1"), "opened again");
|
||||
}
|
||||
|
||||
/// A folder far down the list is scrolled into view.
|
||||
#[test]
|
||||
fn show_in_folders_scrolls_the_folder_into_view() {
|
||||
let paths: Vec<String> = (0..60).map(|i| format!("/pics/f{i:02}/a.jpg")).collect();
|
||||
let refs: Vec<&str> = paths.iter().map(String::as_str).collect();
|
||||
let mut h = folders_app_sized(&refs, [1400.0, 500.0]);
|
||||
activate(&mut h, "/pics/f59/a.jpg");
|
||||
h.app.run("app.showInFolders", json!({})).unwrap();
|
||||
h.settle(SETTLE);
|
||||
let row = widget(&h, "source:libfolder:/pics/f59");
|
||||
assert!(row.top() >= widget(&h, "sidebarTitle").bottom() && row.bottom() <= 500.0, "in view: {row:?}");
|
||||
}
|
||||
|
||||
/// Without an active photo in a library folder there is nothing to show: no photo, a demo scene.
|
||||
#[test]
|
||||
fn show_in_folders_needs_a_photo_in_a_library_folder() {
|
||||
let mut h = folders_app(&["/pics/trip/a.jpg"]);
|
||||
h.app.run("library.select", json!({"ids": []})).unwrap();
|
||||
assert_eq!(h.app.session.active(), None);
|
||||
assert!(!crate::menus::ui_enabled(&h.app, "app.showInFolders"));
|
||||
let entry = crate::menus::menu_entries(&h.app).into_iter().find(|e| e.id == "app.showInFolders").unwrap();
|
||||
assert!(!entry.enabled && entry.menu == ["Photo"], "listed in Photo, disabled");
|
||||
assert!(h.app.run("app.showInFolders", json!({})).is_err());
|
||||
assert_eq!(h.app.session.source, lightcraft_engine::LibrarySource::All, "nothing changed");
|
||||
let mut demo = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services { png: None, ..Default::default() });
|
||||
let scene = demo.session.visible().first().copied().unwrap();
|
||||
demo.run("library.select", json!({"ids": [scene.0]})).unwrap();
|
||||
assert!(!crate::menus::ui_enabled(&demo, "app.showInFolders"), "a demo scene has no folder");
|
||||
}
|
||||
|
||||
/// The grid's menu reveals the photo right-clicked, not the active one a multi-selection keeps.
|
||||
#[test]
|
||||
fn show_in_folders_reveals_the_photo_asked_for_and_follows_to_its_folder() {
|
||||
let mut h = folders_app(&["/pics/trip/a.jpg", "/pics/home/c.jpg"]);
|
||||
let id_of = |h: &Headless, path: &str| {
|
||||
h.app.session.catalog.photos().find(|p| matches!(&p.source, lightcraft_catalog::Source::File { path: f } if f == path)).unwrap().id
|
||||
};
|
||||
let (a, c) = (id_of(&h, "/pics/trip/a.jpg"), id_of(&h, "/pics/home/c.jpg"));
|
||||
h.app.run("library.select", json!({"ids": [a.0, c.0], "active": a.0})).unwrap();
|
||||
h.step();
|
||||
let r = h.app.run("app.showInFolders", json!({"id": c.0})).unwrap();
|
||||
assert_eq!(r["path"], "/pics/home");
|
||||
assert_eq!(h.app.session.library_folder.as_deref(), Some("/pics/home"));
|
||||
assert_eq!(h.app.session.active(), Some(c), "the view of that folder shows the photo asked for");
|
||||
assert!(crate::menus::show_in_folders_enabled(&h.app, Some(c)));
|
||||
}
|
||||
|
||||
/// A folder deeper than the tree goes has no row: the entry is disabled rather than failing.
|
||||
#[test]
|
||||
fn show_in_folders_is_disabled_for_a_folder_below_the_trees_depth() {
|
||||
let deep = format!("/pics{}/a.jpg", "/d".repeat(lightcraft_catalog::folders::MAX_DEPTH + 4));
|
||||
let mut h = folders_app(&[deep.as_str()]);
|
||||
activate(&mut h, &deep);
|
||||
assert!(!h.app.caches.folder_tree(&h.app.session.catalog).is_empty());
|
||||
assert!(!crate::menus::ui_enabled(&h.app, "app.showInFolders"));
|
||||
assert!(h.app.run("app.showInFolders", json!({})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_renamed_folder_keeps_its_place_in_the_tree() {
|
||||
/// Removes the folder however the test ends.
|
||||
|
||||
@@ -51,10 +51,10 @@ cited here still exists. Formerly docs/parity.md.
|
||||
| V. Preferences (PREF) | 6 | 2 | 2 | 2 | 1/1 (100%) | 4/5 (80%) |
|
||||
| W. Cloud & AI infrastructure (CLOUD / AI) | 1 | 0 | 1 | 2 | — | — |
|
||||
| X. Cross-cutting behaviours (BEHAV) | 23 | 6 | 1 | 1 | 8/8 (100%) | 7/10 (70%) |
|
||||
| Y. Menus | 84 | 0 | 4 | 8 | 47/47 (100%) | 23/23 (100%) |
|
||||
| Y. Menus | 85 | 0 | 4 | 8 | 47/47 (100%) | 23/23 (100%) |
|
||||
| Z. Keyboard shortcuts (desktop) | 78 | 4 | 2 | 1 | 49/52 (94%) | 24/26 (92%) |
|
||||
| Lightroom Classic extras | 40 | 24 | 22 | 8 | — | 24/26 (92%) |
|
||||
| **Total** | 419 | 62 | 58 | 35 | 194/201 (97%) | 148/161 (92%) |
|
||||
| **Total** | 420 | 62 | 58 | 35 | 194/201 (97%) | 148/161 (92%) |
|
||||
|
||||
Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **83.5%** of 539 in-scope rows — P0 98.3% of 201 · P1 95.0% of 161 · P2 56.0% of 176.
|
||||
<!-- /parity:summary -->
|
||||
@@ -516,6 +516,7 @@ visible menu bar yet: the menu model is only exposed through the control channel
|
||||
| MENU-FILE-SHARE | Share / get link / invite | OOS | 🚫 | | |
|
||||
| MENU-FILE-PHOTOSHOP | Edit in external editor | P2 | ✅ | `cmd:photo.editInPhotoCraft`, `cmd:photo.editInExternal`, `cmd:photo.openOriginalInExternal` | Photo ▸ Edit In ▸ Edit in PhotoCraft / Edit in Other Editor / Open Original in Other Editor (RAW, no copy), and Edit In on a photo's right-click menu |
|
||||
| MENU-FILE-SHOWFINDER | Show in Finder | P0 | ✅ | `cmd:app.showInFinder` | ⌘R; Explorer on Windows, the folder on Linux; disabled for demo scenes and on the web |
|
||||
| MENU-PHOTO-SHOWFOLDERS | Show in Folders | P2 | ✅ | `cmd:app.showInFolders`, `crates/ui-egui/src/panels/left.rs` (`folder_row_of`), tests `show_in_folders_*` in `tests_panels.rs` | Photo ▸ Show in Folders and the photo right-click menu: selects the active photo's folder in the sidebar's Folders section, opens the rows above it and scrolls to it; opens the Library sidebar if it is behind another tab; disabled for demo scenes and photos outside a library folder |
|
||||
| MENU-FILE-OFFLINE | Store album locally | P2 | 🚫 | | not applicable: local-first |
|
||||
| MENU-FILE-CLOSE | Close Window | P1 | ✅ | `apps/lightcraft/src/native_menu.rs` | the system's Close Window item at the end of File (⌘W) |
|
||||
| MENU-EDIT-UNDO | Undo | P0 | ✅ | `cmd:edit.undo` | label does not name the step |
|
||||
|
||||
Reference in New Issue
Block a user