mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-31 21:48:53 -06:00
LR-IMP-ADD-DIALOG: keep import thumbnail review changes focused (#2237)
Update from main and remove Windows fixtures shared with #2004. Supply both missing labels in all 13 catalogs and use the shared ControlSpec slider for keyboard steps, exact entry and reset. Add localization and idle-keyboard regressions, document control IDs, and retain the partial #2056 scope. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
reviews:
|
||||
auto_review:
|
||||
# Dependabot bumps are checked by CI; skip CodeRabbit reviews on them.
|
||||
ignore_usernames:
|
||||
- "dependabot[bot]"
|
||||
@@ -0,0 +1,30 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain Rust / Cargo dependencies
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "06:00"
|
||||
timezone: "America/New_York"
|
||||
# Consolidate updates into a single PR rather than one PR per crate
|
||||
groups:
|
||||
cargo-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
|
||||
# Maintain GitHub Actions workflows
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "06:00"
|
||||
timezone: "America/New_York"
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
commit-message:
|
||||
prefix: "ci(deps)"
|
||||
@@ -18,11 +18,6 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
eval:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
|
||||
- name: nix flake check --no-build
|
||||
run: nix flake check --no-build --all-systems
|
||||
nix:
|
||||
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
|
||||
uses: storytold/craft-common-actions/.github/workflows/nix.yml@main
|
||||
|
||||
Generated
+33
-33
@@ -3078,7 +3078,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -3103,7 +3103,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-catalog"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"lightcraft-develop",
|
||||
@@ -3120,7 +3120,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-cli"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"dhat",
|
||||
"lightcraft-codecs",
|
||||
@@ -3136,7 +3136,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-codecs"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"avif-serialize",
|
||||
"bytemuck",
|
||||
@@ -3167,7 +3167,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-color"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-geom",
|
||||
"proptest",
|
||||
@@ -3176,7 +3176,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-denoise"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"faer-core",
|
||||
"lightcraft-denoise-core",
|
||||
@@ -3193,7 +3193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-denoise-core"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-raster",
|
||||
"miniz_oxide 0.8.9",
|
||||
@@ -3208,7 +3208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-develop"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-color",
|
||||
"lightcraft-geom",
|
||||
@@ -3220,7 +3220,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-engine"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"craft-docpath",
|
||||
@@ -3263,7 +3263,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-faces"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-testkit",
|
||||
"matrixmultiply",
|
||||
@@ -3278,7 +3278,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-fetch"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"rustls",
|
||||
@@ -3289,7 +3289,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-geocode"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"miniz_oxide 0.8.9",
|
||||
"serde",
|
||||
@@ -3297,7 +3297,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-geom"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"proptest",
|
||||
"serde",
|
||||
@@ -3305,7 +3305,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-gpu"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"lightcraft-color",
|
||||
@@ -3325,7 +3325,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-heif"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"heic-rs",
|
||||
"rayon",
|
||||
@@ -3333,14 +3333,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-inpaint"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-lensdb"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-develop",
|
||||
"lightcraft-geom",
|
||||
@@ -3349,7 +3349,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-mcp"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-codecs",
|
||||
"lightcraft-engine",
|
||||
@@ -3360,7 +3360,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-merge"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-codecs",
|
||||
"lightcraft-color",
|
||||
@@ -3376,7 +3376,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-meta"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-geom",
|
||||
"lightcraft-tiff",
|
||||
@@ -3388,7 +3388,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-pipeline"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"lightcraft-color",
|
||||
@@ -3406,7 +3406,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-preview"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"jpeg-encoder",
|
||||
"lightcraft-raster",
|
||||
@@ -3418,7 +3418,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-raster"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"lightcraft-color",
|
||||
@@ -3430,7 +3430,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-raw"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"dhat",
|
||||
"jpeg-encoder",
|
||||
@@ -3454,7 +3454,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-scenes"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"lightcraft-color",
|
||||
@@ -3465,7 +3465,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-segment"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"candle-core",
|
||||
"candle-nn",
|
||||
@@ -3479,7 +3479,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-slideshow"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"lightcraft-raster",
|
||||
"serde",
|
||||
@@ -3488,15 +3488,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-sysmem"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-testkit"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-tiff"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"proptest",
|
||||
"serde",
|
||||
@@ -3505,7 +3505,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-ui-egui"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"craft-ui",
|
||||
"egui",
|
||||
@@ -3534,7 +3534,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lightcraft-web"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"eframe",
|
||||
@@ -7004,7 +7004,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"flate2",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ resolver = "3"
|
||||
members = ["crates/*", "apps/*", "xtask"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.90"
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
<img alt="MCP server included" src="https://img.shields.io/badge/MCP-ready-8a5800?style=flat-square">
|
||||
<img alt="License: MIT OR Apache-2.0" src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-8a5800?style=flat-square">
|
||||
<a href="ROADMAP.md"><img alt="Status: young and moving fast" src="https://img.shields.io/badge/status-young%20%26%20moving%20fast-f2a516?style=flat-square"></a>
|
||||
<br>
|
||||
<a href="https://sonarcloud.io/project/overview?id=storytold_lightcraft"><img alt="Scanned by SonarCloud" src="https://img.shields.io/badge/Scanned_by-SonarCloud-4b9fd5?style=flat-square&logo=sonarqubecloud&logoColor=white"></a>
|
||||
<a href="https://coderabbit.ai"><img alt="Reviewed by CodeRabbit" src="https://img.shields.io/coderabbit/prs/github/storytold/lightcraft?style=flat-square&label=Reviewed%20by%20CodeRabbit&logo=coderabbit"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@@ -991,19 +991,15 @@ fn same_folder_as_original_exports_next_to_each_photo() {
|
||||
let exists = |p: &str| std::path::Path::new(p).exists();
|
||||
let files = crate::export::export_batch(&mut s, &ids, &o, &to, &mut crate::export::write_file, &exists).unwrap();
|
||||
let written: Vec<&str> = files.iter().map(|f| f["path"].as_str().unwrap()).collect();
|
||||
// Compare path components: Windows accepts both separators in returned paths.
|
||||
let mut want = vec![a.join("Exports").join("IMG_1.jpg"), b.join("Exports").join("IMG_1.jpg")];
|
||||
let mut got: Vec<std::path::PathBuf> = written.iter().map(std::path::PathBuf::from).collect();
|
||||
let mut want = vec![a.join("Exports/IMG_1.jpg").to_string_lossy().to_string(), b.join("Exports/IMG_1.jpg").to_string_lossy().to_string()];
|
||||
let mut got: Vec<String> = written.iter().map(|p| p.to_string()).collect();
|
||||
want.sort();
|
||||
got.sort();
|
||||
assert_eq!(got, want);
|
||||
assert!(!dir.join("unused").exists(), "nothing written to the specific folder");
|
||||
// again: each folder's own collision gets the next free name
|
||||
let files = crate::export::export_batch(&mut s, &ids, &o, &to, &mut crate::export::write_file, &exists).unwrap();
|
||||
assert!(
|
||||
files.iter().all(|f| std::path::Path::new(f["path"].as_str().unwrap()).ends_with(std::path::Path::new("Exports").join("IMG_1-2.jpg"))),
|
||||
"{files:?}"
|
||||
);
|
||||
assert!(files.iter().all(|f| f["path"].as_str().unwrap().ends_with("Exports/IMG_1-2.jpg")), "{files:?}");
|
||||
// no subfolder: next to the original, which is never written over
|
||||
let beside = ExportOptions { subfolder: String::new(), conflict: Conflict::Overwrite, ..o.clone() };
|
||||
let err = crate::export::export_batch(&mut s, &ids, &beside, &to, &mut crate::export::write_file, &exists).unwrap_err();
|
||||
|
||||
@@ -2063,5 +2063,7 @@
|
||||
"Open Settings": "Otevřít nastavení",
|
||||
"Select none": "Zrušit celý výběr",
|
||||
"Select all": "Vybrat vše",
|
||||
"Visualize HDR": "Zobrazit rozsah HDR"
|
||||
"Visualize HDR": "Zobrazit rozsah HDR",
|
||||
"Thumbnail size": "Velikost miniatur",
|
||||
"Import Thumbnail Size": "Velikost miniatur při importu"
|
||||
}
|
||||
|
||||
@@ -2341,5 +2341,7 @@
|
||||
"Checking the GPU…": "GPU wird geprüft …",
|
||||
"CPU": "CPU",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "Ein Gesichtsmodell nutzt die GPU nur, wenn eine Prüfung zeigt, dass sie dieselben Ergebnisse schneller liefert.",
|
||||
"Checking the GPU for faces": "GPU für Gesichter wird geprüft"
|
||||
"Checking the GPU for faces": "GPU für Gesichter wird geprüft",
|
||||
"Thumbnail size": "Vorschaubildgröße",
|
||||
"Import Thumbnail Size": "Vorschaubildgröße beim Import"
|
||||
}
|
||||
|
||||
@@ -2340,5 +2340,7 @@
|
||||
"Checking the GPU…": "Comprobando la GPU…",
|
||||
"CPU": "CPU",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "Un modelo de caras usa la GPU solo cuando una comprobación muestra que da los mismos resultados más rápido.",
|
||||
"Checking the GPU for faces": "Comprobando la GPU para las caras"
|
||||
"Checking the GPU for faces": "Comprobando la GPU para las caras",
|
||||
"Thumbnail size": "Tamaño de las miniaturas",
|
||||
"Import Thumbnail Size": "Tamaño de las miniaturas de importación"
|
||||
}
|
||||
|
||||
@@ -2338,5 +2338,7 @@
|
||||
"Checking the GPU…": "Vérification du GPU…",
|
||||
"CPU": "Processeur",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "Un modèle de visages n’utilise le GPU que si une vérification montre qu’il donne les mêmes résultats plus vite.",
|
||||
"Checking the GPU for faces": "Vérification du GPU pour les visages"
|
||||
"Checking the GPU for faces": "Vérification du GPU pour les visages",
|
||||
"Thumbnail size": "Taille des vignettes",
|
||||
"Import Thumbnail Size": "Taille des vignettes d’importation"
|
||||
}
|
||||
|
||||
@@ -1990,5 +1990,8 @@
|
||||
"Test Face Model": "Testa il modello dei volti",
|
||||
"This build cannot run recognition models: they can be added and chosen, not used.": "Questa versione non può eseguire modelli di riconoscimento: si possono aggiungere e scegliere, non usare.",
|
||||
"This build has nowhere to keep face models, so the model list is not available. (The desktop app has.)": "Questa versione non ha dove tenere i modelli dei volti, quindi l'elenco dei modelli non è disponibile. (L'app desktop ce l'ha.)",
|
||||
"Unnamed Faces": "Volti senza nome"
|
||||
"Unnamed Faces": "Volti senza nome",
|
||||
"Thumbnail size": "Dimensione delle miniature",
|
||||
"Import Thumbnail Size": "Dimensione delle miniature di importazione",
|
||||
"Click to type a value, drag to change it": "Fai clic per digitare un valore, trascina per modificarlo"
|
||||
}
|
||||
|
||||
@@ -2340,5 +2340,7 @@
|
||||
"Checking the GPU…": "GPU を確認中…",
|
||||
"CPU": "CPU",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "顔モデルが GPU を使うのは、同じ結果をより速く出せると確認できた場合だけです。",
|
||||
"Checking the GPU for faces": "顔用の GPU を確認中"
|
||||
"Checking the GPU for faces": "顔用の GPU を確認中",
|
||||
"Thumbnail size": "サムネイルのサイズ",
|
||||
"Import Thumbnail Size": "読み込みサムネイルのサイズ"
|
||||
}
|
||||
|
||||
@@ -2718,5 +2718,8 @@
|
||||
"≥ at least": "≥ არანაკლებ",
|
||||
"⌘-drag to straighten, ⇧G Guided Upright, a grid while transforming.": "⌘-გადათრევა გასასწორებლად, ⇧G მართვადი Upright, ბადე გარდაქმნისას.",
|
||||
"日本語": "日本語",
|
||||
"繁體中文(台灣)": "繁體中文(台灣)"
|
||||
"繁體中文(台灣)": "繁體中文(台灣)",
|
||||
"Thumbnail size": "მინიატურების ზომა",
|
||||
"Import Thumbnail Size": "იმპორტის მინიატურების ზომა",
|
||||
"Click to type a value, drag to change it": "დააწკაპუნეთ მნიშვნელობის შესაყვანად, გადაათრიეთ მის შესაცვლელად"
|
||||
}
|
||||
|
||||
@@ -2157,5 +2157,7 @@
|
||||
"This is a Lightroom catalog, not a LightCraft library. Import it into a LightCraft library instead?": "To jest katalog Lightrooma, a nie biblioteka LightCraft. Zaimportować go zamiast tego do biblioteki LightCraft?",
|
||||
"Unnamed Faces": "Nienazwane twarze",
|
||||
"Vertical inset (% of short edge)": "Margines pionowy (% krótszego boku)",
|
||||
"not added to Apple Photos (cancelled)": "nie dodano do aplikacji Zdjęcia (anulowano)"
|
||||
"not added to Apple Photos (cancelled)": "nie dodano do aplikacji Zdjęcia (anulowano)",
|
||||
"Thumbnail size": "Rozmiar miniatur",
|
||||
"Import Thumbnail Size": "Rozmiar miniatur importu"
|
||||
}
|
||||
|
||||
@@ -2180,5 +2180,13 @@
|
||||
"Checking the GPU…": "Verificando a GPU…",
|
||||
"CPU": "CPU",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "Um modelo de rostos só usa a GPU quando uma verificação mostra que ela dá os mesmos resultados mais rápido.",
|
||||
"Checking the GPU for faces": "Verificando a GPU para rostos"
|
||||
"Checking the GPU for faces": "Verificando a GPU para rostos",
|
||||
"Include selected photos": "Incluir fotos selecionadas",
|
||||
"Color Overlay": "Sobreposição de cor",
|
||||
"Color Overlay on B&W": "Sobreposição de cor em P&B",
|
||||
"Image on Black": "Imagem sobre preto",
|
||||
"Image on White": "Imagem sobre branco",
|
||||
"White on Black": "Branco sobre preto",
|
||||
"Thumbnail size": "Tamanho das miniaturas",
|
||||
"Import Thumbnail Size": "Tamanho das miniaturas de importação"
|
||||
}
|
||||
|
||||
@@ -2333,5 +2333,7 @@
|
||||
"Checking the GPU…": "Проверка ГП…",
|
||||
"CPU": "ЦП",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "Модель лиц использует ГП, только если проверка показала, что он даёт те же результаты быстрее.",
|
||||
"Checking the GPU for faces": "Проверка ГП для лиц"
|
||||
"Checking the GPU for faces": "Проверка ГП для лиц",
|
||||
"Thumbnail size": "Размер миниатюр",
|
||||
"Import Thumbnail Size": "Размер миниатюр при импорте"
|
||||
}
|
||||
|
||||
@@ -2338,5 +2338,7 @@
|
||||
"Checking the GPU…": "Перевірка ГП…",
|
||||
"CPU": "ЦП",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "Модель облич використовує ГП, лише якщо перевірка показала, що він дає ті самі результати швидше.",
|
||||
"Checking the GPU for faces": "Перевірка ГП для облич"
|
||||
"Checking the GPU for faces": "Перевірка ГП для облич",
|
||||
"Thumbnail size": "Розмір мініатюр",
|
||||
"Import Thumbnail Size": "Розмір мініатюр імпорту"
|
||||
}
|
||||
|
||||
@@ -2179,5 +2179,7 @@
|
||||
"Checking the GPU…": "正在检查 GPU…",
|
||||
"CPU": "CPU",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "只有检查表明 GPU 能更快给出相同结果时,人脸模型才会使用 GPU。",
|
||||
"Checking the GPU for faces": "正在检查用于人脸的 GPU"
|
||||
"Checking the GPU for faces": "正在检查用于人脸的 GPU",
|
||||
"Thumbnail size": "缩略图大小",
|
||||
"Import Thumbnail Size": "导入缩略图大小"
|
||||
}
|
||||
|
||||
@@ -2141,5 +2141,7 @@
|
||||
"Checking the GPU…": "正在檢查 GPU…",
|
||||
"CPU": "CPU",
|
||||
"A face model uses the GPU only when a check shows it gives the same results faster.": "只有在檢查顯示 GPU 能更快得出相同結果時,臉孔模型才會使用 GPU。",
|
||||
"Checking the GPU for faces": "正在檢查用於臉孔的 GPU"
|
||||
"Checking the GPU for faces": "正在檢查用於臉孔的 GPU",
|
||||
"Thumbnail size": "縮圖大小",
|
||||
"Import Thumbnail Size": "匯入縮圖大小"
|
||||
}
|
||||
|
||||
@@ -52,6 +52,26 @@ fn thumb_edge(size: f32, pixels_per_point: f32) -> usize {
|
||||
[192, 384, 512, 768, 1024].into_iter().find(|edge| *edge as f32 >= pixels).unwrap_or(1024)
|
||||
}
|
||||
|
||||
/// Use the shared control contract for pointer, keyboard, exact-value entry and reset interactions.
|
||||
fn thumb_size_slider(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
use lightcraft_develop::controls::{ControlSpec, Section, Track};
|
||||
const SIZE: ControlSpec = ControlSpec {
|
||||
id: "importThumbSize",
|
||||
label: "Thumbnail size",
|
||||
section: Section::Light,
|
||||
min: THUMB_SIZE_MIN as f64,
|
||||
max: THUMB_SIZE_MAX as f64,
|
||||
default: THUMB_SIZE_DEFAULT as f64,
|
||||
step: 1.0,
|
||||
decimals: 0,
|
||||
track: Track::Plain,
|
||||
};
|
||||
let out = crate::widgets::slider(ui, &SIZE, f64::from(thumb_size(app.ui.import_thumb_size)), true, None);
|
||||
if let Some(size) = out.value {
|
||||
let _ = app.run("view.importThumbSize", json!({"size": SIZE.clamp(size)}));
|
||||
}
|
||||
}
|
||||
|
||||
/// Height of the photo grid: what is `available` once `below` (options and buttons) is left free,
|
||||
/// in whole points so the window never grows by a fraction each frame, and at least one `row`.
|
||||
pub(crate) fn grid_height(available: f32, below: f32, row: f32) -> f32 {
|
||||
@@ -1043,16 +1063,7 @@ pub fn body(app: &mut LightcraftApp, ui: &mut egui::Ui, d: &mut ImportDialog) {
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = thumb_size(app.ui.import_thumb_size);
|
||||
let response =
|
||||
ui.add(egui::Slider::new(&mut size, THUMB_SIZE_MIN..=THUMB_SIZE_MAX).text(crate::i18n::tr("Thumbnail size")).show_value(false));
|
||||
// Slider's response includes its label; automation addresses the track itself.
|
||||
register(ui.ctx(), "slider:importThumbSize", Rect::from_min_size(response.rect.min, vec2(ui.spacing().slider_width, response.rect.height())));
|
||||
if response.changed() {
|
||||
let _ = app.run("view.importThumbSize", json!({"size": size}));
|
||||
}
|
||||
});
|
||||
thumb_size_slider(app, ui);
|
||||
// candidate grid
|
||||
let cell = thumb_size(app.ui.import_thumb_size).min(ui.available_width().max(1.0));
|
||||
let avail = ui.available_width();
|
||||
|
||||
@@ -1263,6 +1263,7 @@ pub struct SyncCounts {
|
||||
}
|
||||
|
||||
impl Default for UiState {
|
||||
/// Initialize the library and import review sizes as independent app preferences.
|
||||
fn default() -> Self {
|
||||
UiState {
|
||||
window: None,
|
||||
|
||||
@@ -50,13 +50,8 @@ fn widget(h: &Headless, id: &str) -> Option<Rect> {
|
||||
h.app.widgets.iter().find(|(w, _)| w == id).map(|(_, r)| *r)
|
||||
}
|
||||
|
||||
/// Match FolderNode widget ids, whose paths use forward slashes on every platform.
|
||||
fn folder_id(prefix: &str, dir: &Path) -> String {
|
||||
format!("{prefix}:{}", dir.to_string_lossy().replace('\\', "/"))
|
||||
}
|
||||
|
||||
fn folder_row(h: &Headless, dir: &Path) -> Option<Rect> {
|
||||
widget(h, &folder_id("source:libfolder", dir))
|
||||
widget(h, &format!("source:libfolder:{}", dir.to_string_lossy()))
|
||||
}
|
||||
|
||||
fn id_of(h: &Headless, file: &str) -> u64 {
|
||||
@@ -185,7 +180,7 @@ fn photos_already_in_the_folder_stay_put() {
|
||||
let (root, trip, home) = folders("here");
|
||||
let mut h = app(&[(trip.join("beach.png"), false), (home.join("garden.png"), false)]);
|
||||
// open the folder holding both
|
||||
let toggle = widget(&h, &folder_id("libraryFolderToggle", &root)).unwrap();
|
||||
let toggle = widget(&h, &format!("libraryFolderToggle:{}", root.to_string_lossy())).unwrap();
|
||||
h.app.synthetic.push(egui::Event::PointerMoved(toggle.center()));
|
||||
h.app.synthetic.push(egui::Event::PointerButton {
|
||||
pos: toggle.center(),
|
||||
@@ -289,7 +284,7 @@ fn cancelling_the_question_moves_nothing() {
|
||||
let target = if folder_row(&h, &home).is_some() {
|
||||
target
|
||||
} else {
|
||||
click(&mut h, &folder_id("libraryFolderToggle", &root));
|
||||
click(&mut h, &format!("libraryFolderToggle:{}", root.to_string_lossy()));
|
||||
folder_row(&h, &home).unwrap()
|
||||
};
|
||||
let undo = h.app.session.undo.len();
|
||||
@@ -307,7 +302,7 @@ fn dont_ask_again_moves_the_next_drop_at_once() {
|
||||
let (root, trip, home) = folders("dont-ask");
|
||||
std::fs::copy(trip.join("beach.png"), trip.join("dunes.png")).unwrap();
|
||||
let mut h = app(&[(trip.join("beach.png"), false), (trip.join("dunes.png"), false), (home.join("garden.png"), false)]);
|
||||
click(&mut h, &folder_id("libraryFolderToggle", &root));
|
||||
click(&mut h, &format!("libraryFolderToggle:{}", root.to_string_lossy()));
|
||||
let target = folder_row(&h, &home).unwrap();
|
||||
let beach = id_of(&h, "beach.png");
|
||||
h.app.session.selection = lightcraft_engine::Selection::single(lightcraft_catalog::PhotoId(beach));
|
||||
@@ -332,7 +327,7 @@ fn dont_ask_again_moves_the_next_drop_at_once() {
|
||||
fn a_question_left_open_while_another_library_opens_moves_nothing() {
|
||||
let (root, trip, home) = folders("other-library");
|
||||
let mut h = app(&[(trip.join("beach.png"), false), (home.join("garden.png"), false)]);
|
||||
click(&mut h, &folder_id("libraryFolderToggle", &root));
|
||||
click(&mut h, &format!("libraryFolderToggle:{}", root.to_string_lossy()));
|
||||
let beach = id_of(&h, "beach.png");
|
||||
let target = folder_row(&h, &home).unwrap();
|
||||
drop_beach(&mut h, beach, target);
|
||||
|
||||
@@ -157,3 +157,52 @@ fn import_thumbnail_size_rejects_bad_commands_and_restores_old_preferences() {
|
||||
assert_eq!(state.import_thumb_size, 116.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// A selected import slider keeps keyboard nudges after idle frames without changing candidate checks.
|
||||
#[test]
|
||||
fn import_thumbnail_slider_keeps_the_shared_keyboard_contract() {
|
||||
let mut review = Review::new(1.0);
|
||||
let checked = review.checks();
|
||||
review.request("ui.clickWidget", json!({"id": "slider:importThumbSize", "fx": 0.4}));
|
||||
assert!(review.h.settle(T));
|
||||
for _ in 0..6 {
|
||||
review.h.step();
|
||||
}
|
||||
let before = review.h.app.ui.import_thumb_size;
|
||||
review.request("ui.key", json!({"key": "ArrowUp"}));
|
||||
assert!(review.h.settle(T));
|
||||
assert_eq!(review.h.app.ui.import_thumb_size, before + 1.0);
|
||||
assert_eq!(review.checks(), checked);
|
||||
}
|
||||
|
||||
/// Every shipped language translates the slider and command labels, with the real control visible at high density.
|
||||
#[test]
|
||||
fn import_thumbnail_labels_are_localized_in_every_shipped_language() {
|
||||
use crate::i18n::Locale;
|
||||
let command =
|
||||
crate::menus::UI_COMMANDS.iter().find(|command| command.0 == "view.importThumbSize").expect("the import-size command is registered");
|
||||
assert_eq!(command.1, "Import Thumbnail Size", "the catalog key matches the command's actual label");
|
||||
for theme in ["charcoal", "paper"] {
|
||||
for scale in [1.0, 2.25] {
|
||||
let mut app = LightcraftApp::new(lightcraft_engine::Session::new(), Services { png: None, ..Default::default() });
|
||||
app.ui.dialog = Some(Dialog::Import { opts: Box::new(ImportDialog::new(Vec::new())) });
|
||||
let mut h = Headless::new(app, [960.0, 720.0], scale);
|
||||
assert!(h.app.run("view.theme", json!({"theme": theme})).is_ok());
|
||||
for locale in Locale::ALL {
|
||||
for source in ["Thumbnail size", "Import Thumbnail Size", "Click to type a value, drag to change it"] {
|
||||
assert!(!locale.tr(source).is_empty());
|
||||
if *locale != Locale::En {
|
||||
assert_ne!(locale.tr(source), source, "{locale:?}: {source}");
|
||||
}
|
||||
}
|
||||
let result = h.request("ui.set", json!({"language": locale.code()}), T);
|
||||
assert_eq!(result["ok"], true, "{result}");
|
||||
assert!(h.settle(T));
|
||||
assert!(h.painted_text().iter().any(|text| text == locale.tr("Thumbnail size")), "{locale:?}: translated slider label");
|
||||
let track = h.app.widgets.iter().find(|(id, _)| id == "slider:importThumbSize").expect("the import slider is visible").1;
|
||||
let viewport = egui::Rect::from_min_size(egui::Pos2::ZERO, h.size);
|
||||
assert!(viewport.contains_rect(track), "{locale:?} {theme} {scale}: slider stays inside the viewport");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +244,10 @@ The import review's **Thumbnail size** slider is `slider:importThumbSize` in `ui
|
||||
(80–320, clamped); omitting `size` reads it. Both return `{size}`. Non-numeric or non-finite
|
||||
values are refused. The preference is `importThumbSize` in the UI state, independent of the
|
||||
library grid's `thumbSize`; resizing keeps the import checkboxes and duplicate markers.
|
||||
The shared slider also exposes `sliderValue:importThumbSize` for exact entry and
|
||||
`sliderLabel:importThumbSize` for double-click reset to 116 pt. Up/Down nudges the selected
|
||||
slider by 1 pt; Return commits typed values and Escape cancels them. Slider and command
|
||||
labels, including edit/reset feedback, are localized in every shipped catalog.
|
||||
|
||||
## Headless rendering (no window, no GPU)
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# Gaps: where LightCraft falls short of Lightroom
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-18 · **Change:** minor (import grid thumbnail size adjustable; gap 30 tracks the remaining pre-import loupe, #2056; upstream Print Page Options and camera updates retained)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-21 · **Change:** patch (import thumbnail localization and shared slider interactions; gap/status counts unchanged)
|
||||
|
||||
Every known shortfall, one entry each, ranked by user impact (how many Lightroom users hit it, and
|
||||
how hard it blocks them), then by effort. **Agents pick work from the top** (the first entry nobody is
|
||||
@@ -81,6 +81,7 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-21 | patch | LR-IMP-ADD-DIALOG: translate the import thumbnail labels in all 13 catalogs; use the shared ControlSpec slider for keyboard, exact entry and reset; retain the remaining pre-import loupe gap and unchanged counts |
|
||||
| 2026-10-17 | minor | Import thumbnail-size slider (part of #2056); gap 30 records the remaining pre-import loupe and keyboard culling |
|
||||
| 2026-10-18 | patch | Gap 1: six X-Trans CMOS 4 Fujifilm bodies (X-T30 …) take the X-T3's spectral matrices instead of the neutral matrix |
|
||||
| 2026-10-15 | minor | Gap 15 shrinks: the Page panel's Page Options (Page Numbers, Page Info, Crop Marks) print on Single Image and Contact Sheet pages; identity plate, watermark, photo info, Cut Guides, direct printing and JPEG output remain |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Lightroom parity checklist
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** minor (import grid thumbnail-size slider, #2056; upstream Print Page Options and raw/export updates retained; status and summary counts unchanged) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** patch (import thumbnail localization and shared slider interactions; gap/status counts unchanged) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
|
||||
The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we
|
||||
have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good
|
||||
@@ -797,6 +797,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-21 | patch | LR-IMP-ADD-DIALOG: translate the import thumbnail labels in all 13 catalogs; use the shared ControlSpec slider for keyboard, exact entry and reset; retain the remaining pre-import loupe gap and unchanged counts |
|
||||
| 2026-10-17 | minor | LR-IMP-ADD-DIALOG: import grid thumbnail-size slider, density-aware previews, bounded visible textures and preserved culling; remaining loupe tracked in gap 30 (#2056); status and summary counts unchanged |
|
||||
| 2026-10-14 | patch | LR-EXP-TYPE: opaque 8-bit PNG/TIFF/WebP use RGB; transparent exports retain alpha (#1670). Statuses and feature counts unchanged. |
|
||||
| 2026-10-15 | minor | LRC-PRINT-PAGE: Page Options (Page Numbers, Page Info, Crop Marks) on Single Image and Contact Sheet pages, in the Print dialog, `print.set` and templates; still 🟡 (no identity plate, watermark, photo info or Cut Guides) |
|
||||
|
||||
Reference in New Issue
Block a user