Skip to content

Commit 4488163

Browse files
committed
cleanup: various polish, drop dead settings field, stronger typing
Simplify make_group_key to just path.with_extension(""). Drop group_associated_files entirely since it never shipped. Introduce GroupId type alias instead of bare strings. Rename the filter label to 'Group RAW + JPEG' for clarity. Tighten up comments.
1 parent 97fa495 commit 4488163

5 files changed

Lines changed: 37 additions & 53 deletions

File tree

src-tauri/src/file_management.rs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,6 @@ pub struct AppSettings {
397397
pub waveform_height: Option<u32>,
398398
#[serde(default)]
399399
pub active_waveform_channel: Option<String>,
400-
/// Deprecated: grouping is now driven by the rawStatus filter.
401-
/// Kept for backward-compatible deserialization of existing settings.
402-
#[serde(default, skip_serializing)]
403-
pub group_associated_files: Option<bool>,
404400
#[serde(default)]
405401
pub group_preferred_type: Option<String>,
406402
}
@@ -469,7 +465,6 @@ impl Default for AppSettings {
469465
is_waveform_visible: Some(false),
470466
waveform_height: Some(220),
471467
active_waveform_channel: Some("luma".to_string()),
472-
group_associated_files: Some(false),
473468
group_preferred_type: Some("raw".to_string()),
474469
}
475470
}
@@ -487,21 +482,16 @@ pub struct ImageFile {
487482
group_id: Option<String>,
488483
}
489484

490-
/// Build a grouping key from a source image path: "{parent_dir}/{stem}".
491-
/// Files sharing this key in the same directory are variants of the same shot.
492-
/// Stems are compared case-sensitively. On case-insensitive filesystems
493-
/// (macOS, Windows) this matches OS behavior; on Linux, mixed-case stems
494-
/// from the same camera are practically nonexistent.
485+
/// Grouping key from a source image path: the full path with the
486+
/// extension stripped. Files sharing this key are variants of the
487+
/// same shot. Case-sensitive.
495488
fn make_group_key(source_path: &Path) -> String {
496-
let parent = source_path.parent().unwrap_or(Path::new(""));
497-
let stem = source_path.file_stem().unwrap_or_default();
498-
format!("{}/{}", parent.to_string_lossy(), stem.to_string_lossy())
489+
source_path.with_extension("").to_string_lossy().into_owned()
499490
}
500491

501-
/// Assign `group_id` to files that share a stem with another file in the same
502-
/// directory. Virtual copies are excluded from counting (so one file + its VC
503-
/// don't form a false group) but included in assignment (they inherit the
504-
/// group_id of their source).
492+
/// Tag files that share a stem with `group_id`. Virtual copies are
493+
/// excluded from counting (one file + its virtual copy don't form a
494+
/// group) but still get assigned the group_id of their source.
505495
fn assign_group_ids(files: &mut Vec<ImageFile>) {
506496
let mut stem_sources: HashMap<String, HashSet<PathBuf>> = HashMap::new();
507497

@@ -2605,12 +2595,9 @@ pub fn delete_files_with_associated(paths: Vec<String>) -> Result<(), String> {
26052595
let entry_filename_str = entry_filename.to_string_lossy();
26062596

26072597
if entry_filename_str.ends_with(".rrdata") {
2608-
// Sidecars are named {image_filename}.rrdata or
2609-
// {image_filename}.{vc_id}.rrdata. Extract the image
2610-
// stem by finding the image filename prefix then
2611-
// taking its stem.
2598+
// Sidecars: {filename}.rrdata or {filename}.{vc_id}.rrdata
2599+
// VC ids are first 6 chars of a UUID v4, see create_virtual_copy()
26122600
let without_rrdata = entry_filename_str.trim_end_matches(".rrdata");
2613-
// Strip optional VC id suffix (6 hex chars after a dot)
26142601
let image_filename = if let Some(dot_pos) = without_rrdata.rfind('.') {
26152602
let suffix = &without_rrdata[dot_pos + 1..];
26162603
if suffix.len() == 6 && suffix.chars().all(|c| c.is_ascii_hexdigit()) {

src/App.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ import {
116116
ThumbnailSize,
117117
ThumbnailAspectRatio,
118118
CullingSuggestions,
119+
GroupId,
119120
GroupPreference,
120121
} from './components/ui/AppProperties';
121122
import { buildImageGroups, getVariantLabel, GroupingResult } from './utils/imageGrouping';
@@ -1050,10 +1051,10 @@ function App() {
10501051
return buildImageGroups(imageList, groupPreferredType);
10511052
}, [imageList, isGroupingActive, groupPreferredType]);
10521053

1053-
// Badge info for grouped thumbnails: count + label like "RAF+JPG"
1054-
const groupBadgeInfo: Record<string, { count: number; label: string }> = useMemo(() => {
1054+
// Per-group badge data for thumbnails (count + extension label like "RAF+JPG")
1055+
const groupBadgeInfo: Record<GroupId, { count: number; label: string }> = useMemo(() => {
10551056
if (!groupingResult) return {};
1056-
const info: Record<string, { count: number; label: string }> = {};
1057+
const info: Record<GroupId, { count: number; label: string }> = {};
10571058
for (const [groupId, group] of groupingResult.groups) {
10581059
const extensions = [...new Set(group.variants.map((v) => getVariantLabel(v.path)))];
10591060
info[groupId] = {
@@ -1064,9 +1065,8 @@ function App() {
10641065
return info;
10651066
}, [groupingResult]);
10661067

1067-
// Variant options for the editor toolbar switcher.
1068-
// Suppress for virtual copies: the pills would point to originals,
1069-
// silently leaving the VC context.
1068+
// Variant pills for the editor toolbar. Suppressed for VCs since the
1069+
// pills point to originals and would silently leave the VC context.
10701070
const variantOptions = useMemo(() => {
10711071
if (!selectedImage || !groupingResult) return [];
10721072
const imageFile = imageList.find((img) => img.path === selectedImage.path);
@@ -1079,7 +1079,7 @@ function App() {
10791079
}));
10801080
}, [selectedImage?.path, groupingResult, imageList]);
10811081

1082-
// Filmstrip: when viewing a non-primary variant, highlight the group's primary
1082+
// Highlight the group primary in the filmstrip even when viewing a non-primary variant
10831083
const filmstripActivePath = useMemo(() => {
10841084
if (!selectedImage) return null;
10851085
if (!groupingResult) return selectedImage.path;
@@ -3933,8 +3933,7 @@ function App() {
39333933

39343934
const hasAssociatedFiles = finalSelection.some((selectedPath) => {
39353935
const image = imageList.find((img) => img.path === selectedPath);
3936-
// group_id is always set by Rust when >= 2 files share a stem,
3937-
// regardless of whether the grouping UI is active.
3936+
// Rust sets group_id when >= 2 files share a stem, independent of the UI toggle
39383937
return image?.group_id != null;
39393938
});
39403939

src/components/panel/MainLibrary.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { ThemeProps, THEMES, DEFAULT_THEME_ID } from '../../utils/themes';
2929
import {
3030
AppSettings,
3131
FilterCriteria,
32+
GroupId,
3233
GroupPreference,
3334
ImageFile,
3435
Invokes,
@@ -115,7 +116,7 @@ interface MainLibraryProps {
115116
thumbnailSize: ThumbnailSize;
116117
onNavigateToCommunity(): void;
117118
groupPreferredType: GroupPreference;
118-
groupBadgeInfo?: Record<string, { count: number; label: string }>;
119+
groupBadgeInfo?: Record<GroupId, { count: number; label: string }>;
119120
onGroupPreferredTypeChange(type: GroupPreference): void;
120121
}
121122

@@ -203,7 +204,7 @@ const rawStatusOptions: Array<KeyValueLabel> = [
203204
{ key: RawStatus.All, label: 'All Types' },
204205
{ key: RawStatus.RawOnly, label: 'RAW Only' },
205206
{ key: RawStatus.NonRawOnly, label: 'Non-RAW Only' },
206-
{ key: RawStatus.GroupVariants, label: 'Group Variants' },
207+
{ key: RawStatus.GroupVariants, label: 'Group RAW + JPEG' },
207208
];
208209

209210
const thumbnailSizeOptions: Array<ThumbnailSizeOption> = [

src/components/ui/AppProperties.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,7 @@ export enum RawStatus {
9494
All = 'all',
9595
NonRawOnly = 'nonRawOnly',
9696
RawOnly = 'rawOnly',
97-
/** @deprecated Use GroupVariants instead. Kept for settings migration. */
98-
RawOverNonRaw = 'rawOverNonRaw',
97+
RawOverNonRaw = 'rawOverNonRaw', // migration: old saved settings may have this value
9998
GroupVariants = 'groupVariants',
10099
}
101100

@@ -156,7 +155,6 @@ export interface AppSettings {
156155
isWaveformVisible?: boolean;
157156
waveformHeight?: number;
158157
activeWaveformChannel?: string;
159-
groupAssociatedFiles?: boolean;
160158
groupPreferredType?: GroupPreference;
161159
}
162160

@@ -184,6 +182,9 @@ export interface Folder {
184182
imageCount?: number;
185183
}
186184

185+
/** Opaque group identifier: "{parent_dir}/{file_stem}" assigned by the backend. */
186+
export type GroupId = string;
187+
187188
export interface ImageFile {
188189
is_edited: boolean;
189190
is_raw: boolean;
@@ -192,13 +193,13 @@ export interface ImageFile {
192193
path: string;
193194
tags: Array<string> | null;
194195
exif: { [key: string]: string } | null;
195-
group_id: string | null;
196+
group_id: GroupId | null;
196197
}
197198

198199
export type GroupPreference = 'jpeg' | 'raw';
199200

200201
export interface ImageGroup {
201-
groupId: string;
202+
groupId: GroupId;
202203
primary: ImageFile;
203204
variants: ImageFile[];
204205
virtualCopies: ImageFile[];

src/utils/imageGrouping.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
import { GroupPreference, ImageFile, ImageGroup } from '../components/ui/AppProperties';
1+
import { GroupId, GroupPreference, ImageFile, ImageGroup } from '../components/ui/AppProperties';
22

33
export interface GroupingResult {
4-
/** Map from group_id to group info. */
5-
groups: Map<string, ImageGroup>;
6-
/** Image list with non-primary group variants removed. */
4+
groups: Map<GroupId, ImageGroup>;
5+
/** Image list with non-primary variants filtered out. */
76
displayList: ImageFile[];
87
}
98

109
/**
11-
* Group images by their Rust-assigned group_id and pick a primary per group
12-
* based on user preference. Returns both the group map (for lookups) and
13-
* the collapsed display list (for rendering in the grid/filmstrip).
14-
*
15-
* Virtual copies are attached to their source group but always remain visible
16-
* in the display list (never collapsed).
10+
* Bucket images by their backend-assigned group_id, pick a primary per
11+
* group, return a collapsed display list. Virtual copies stay visible
12+
* (never collapsed).
1713
*/
1814
export function buildImageGroups(
1915
images: ImageFile[],
2016
preference: GroupPreference,
2117
): GroupingResult {
22-
const buckets = new Map<string, { files: ImageFile[]; vcs: ImageFile[] }>();
18+
const buckets = new Map<GroupId, { files: ImageFile[]; vcs: ImageFile[] }>();
2319

2420
for (const image of images) {
2521
if (!image.group_id) continue;
@@ -37,7 +33,7 @@ export function buildImageGroups(
3733
}
3834
}
3935

40-
const groups = new Map<string, ImageGroup>();
36+
const groups = new Map<GroupId, ImageGroup>();
4137
const groupedPaths = new Set<string>();
4238

4339
for (const [groupId, bucket] of buckets) {
@@ -82,16 +78,16 @@ function pickPrimary(files: ImageFile[], preference: GroupPreference): ImageFile
8278
}
8379
}
8480

85-
/** Extract the file extension from a path, lowercased. */
81+
/** File extension from a path, lowercased. Handles ?vc= suffixes. */
8682
export function getFileExtension(path: string): string {
87-
// Strip any ?vc= suffix before looking for the extension
83+
// Strip ?vc= suffix before extracting the extension
8884
const clean = path.split('?')[0];
8985
const dot = clean.lastIndexOf('.');
9086
if (dot === -1) return '';
9187
return clean.substring(dot + 1).toLowerCase();
9288
}
9389

94-
/** Get a display label for a variant (e.g., "RAF", "JPG", "DNG"). */
90+
/** Display label for a variant: uppercase extension (e.g. "RAF", "JPG"). */
9591
export function getVariantLabel(path: string): string {
9692
const ext = getFileExtension(path);
9793
return ext ? ext.toUpperCase() : 'FILE';

0 commit comments

Comments
 (0)