Add the duplicate originals review, from the sidebar and from Settings

"Duplicate originals" appears under the trash in the collections
sidebar while the catalog holds any, and Settings says how many there
are beside the other whole-library passes. Both open one page: every
group with its picture and paths, the copy that stays (tap another path
to change it), a per-group Include box, what the survivor will gain and
any flag, label or face conflict, and why a group was skipped.

The summary is the dry run -- "N groups, M files to trash, K skipped" --
and nothing moves until "Check" has read the copies and "Move M copies
to trash" is pressed. Both run on workers with progress on the page, in
the activity register and, for the move, on the library status line;
Stop ends a job between groups. When it ends the grid, the sidebar and
the trash are refreshed and the survivors' judgements are written to
their sidecars and XMP the way a rating keystroke writes them.

The page is paginated at 30 groups, so a redraw decodes 30 thumbnails
and previews 30 merges whatever the size of the library. Back and
Escape leave it like its own Back button.
This commit is contained in:
2026-09-26 07:19:13 -04:00
parent 8d4ecb75c1
commit 220e9af222
12 changed files with 1413 additions and 9 deletions
+45
View File
@@ -105,6 +105,12 @@ export component SettingsPage inherits Rectangle {
in property <[string]> face-detector-labels;
in property <int> face-detector-selected: 0;
callback face-detector-picked(int);
/// TRACES: FR-CAT-11a
/// How many groups of duplicate originals the catalog holds, and the way
/// into their review. Here beside the other whole-library passes, and on
/// the sidebar under the trash, where the copies it moves will go.
in property <int> duplicate-groups: 0;
callback find-duplicates();
// --- cache ---------------------------------------------------------
in-out property <string> original-budget;
@@ -500,6 +506,45 @@ export component SettingsPage inherits Rectangle {
}
}
// TRACES: FR-CAT-11a
// Duplicate originals: the same file catalogued in
// several folders. Opens the review; nothing moves
// from here.
if root.library-open: Rectangle {
height: Theme.gap-sm;
}
if root.library-open: Caption {
text: root.duplicate-groups > 0
? "Duplicate originals: " + root.duplicate-groups
+ (root.duplicate-groups == 1 ? " file is" : " files are")
+ " in the library more than once. The review "
+ "checks each group is the same file, keeps one "
+ "copy with every rating, keyword and collection "
+ "merged onto it, and moves the rest to the trash."
: "Duplicate originals: none. The same file in two "
+ "folders would be listed here.";
wrap: word-wrap;
}
// GESTURE: Review duplicate originals
// where: Settings
// touch: Tap "Review duplicate originals"
// pointer: Click "Review duplicate originals"
// why: Beside the other whole-library passes, because
// it is one; the sidebar offers the same page
// under the trash.
// manual: duplicate-originals
if root.library-open && root.duplicate-groups > 0: Rectangle {
height: Theme.control-height;
Button {
x: 0;
text: "Review duplicate originals";
clicked => { root.find-duplicates(); }
}
}
if root.activity-kept > 0: Rectangle {
height: Theme.control-height;