chore: post-review hardening + changelog link tooltip
Security / robustness / a11y polish on top of the color-search + tooltips work: - URL opening: route through validated backend commands (open_map_location with lat/lon bounds-checking, open_changelog_url with a fixed URL) instead of the frontend opener:allow-open-url capability, which is now removed. - EXIF GPS parsing: validate coordinate ranges and require the correct N/S/E/W hemisphere ref byte, then clamp. - Guard double-submit on album create / add-to-album (Lightbox, BulkActionBar, Sidebar) and discard stale autocomplete responses in the bulk tag editor. - Gallery tile: stop nesting <button>s — non-interactive tile div with an overlay button for open/toggle; checkbox and Similar promoted with z-index + focus rings. - Accessibility: keyboard handlers, focus-visible rings, and aria on album rows, tag-manage actions, and tile controls; Tooltip uses a block <div> wrapper in block mode and aria-hidden when hidden. - Show the destination URL in a tooltip on the "Full changelog" link so the user can see where it goes before clicking. - Toolbar "All" filter also clears the color filter; color-filtered views no longer get unfiltered newly-indexed images injected. - Sidebar reorder: bail if the album set changed mid-drag. - Tooling: add cargo fmt scripts; alphabetize package.json scripts.
This commit is contained in:
+66
-13
@@ -1702,25 +1702,37 @@ pub struct ImageExif {
|
||||
}
|
||||
|
||||
fn gps_coord(exif: &exif::Exif, coord: exif::Tag, reference: exif::Tag) -> Option<f64> {
|
||||
let (max_abs, positive_ref, negative_ref) = match (coord, reference) {
|
||||
(exif::Tag::GPSLatitude, exif::Tag::GPSLatitudeRef) => (90.0, b'N', b'S'),
|
||||
(exif::Tag::GPSLongitude, exif::Tag::GPSLongitudeRef) => (180.0, b'E', b'W'),
|
||||
_ => return None,
|
||||
};
|
||||
let field = exif.get_field(coord, exif::In::PRIMARY)?;
|
||||
if let exif::Value::Rational(ref parts) = field.value {
|
||||
if parts.len() >= 3 {
|
||||
let degrees = parts[0].to_f64() + parts[1].to_f64() / 60.0 + parts[2].to_f64() / 3600.0;
|
||||
if !degrees.is_finite() || degrees < 0.0 || degrees > max_abs {
|
||||
return None;
|
||||
}
|
||||
// Read the hemisphere straight from the ref tag's ASCII bytes
|
||||
// ("N"/"S"/"E"/"W") rather than its formatted display string.
|
||||
let negative = exif
|
||||
.get_field(reference, exif::In::PRIMARY)
|
||||
.map(|f| match &f.value {
|
||||
exif::Value::Ascii(values) => values
|
||||
.iter()
|
||||
.flatten()
|
||||
.next()
|
||||
.map(|&byte| byte == b'S' || byte == b'W')
|
||||
.unwrap_or(false),
|
||||
_ => false,
|
||||
})
|
||||
.unwrap_or(false);
|
||||
return Some(if negative { -degrees } else { degrees });
|
||||
let ref_byte =
|
||||
exif.get_field(reference, exif::In::PRIMARY)
|
||||
.and_then(|f| match &f.value {
|
||||
exif::Value::Ascii(values) => values.iter().flatten().next().copied(),
|
||||
_ => None,
|
||||
})?;
|
||||
if ref_byte != positive_ref && ref_byte != negative_ref {
|
||||
return None;
|
||||
}
|
||||
let signed = if ref_byte == negative_ref {
|
||||
-degrees
|
||||
} else {
|
||||
degrees
|
||||
};
|
||||
return signed
|
||||
.is_finite()
|
||||
.then_some(signed.clamp(-max_abs, max_abs));
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -2500,6 +2512,47 @@ pub async fn open_app_data_folder(app: AppHandle) -> Result<(), String> {
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct OpenMapLocationParams {
|
||||
pub lat: f64,
|
||||
pub lon: f64,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_map_location(
|
||||
app: AppHandle,
|
||||
params: OpenMapLocationParams,
|
||||
) -> Result<(), String> {
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
if !params.lat.is_finite()
|
||||
|| !params.lon.is_finite()
|
||||
|| !(-90.0..=90.0).contains(¶ms.lat)
|
||||
|| !(-180.0..=180.0).contains(¶ms.lon)
|
||||
{
|
||||
return Err("Invalid map coordinates".to_string());
|
||||
}
|
||||
|
||||
let url = format!(
|
||||
"https://www.openstreetmap.org/?mlat={lat:.6}&mlon={lon:.6}#map=15/{lat:.6}/{lon:.6}",
|
||||
lat = params.lat,
|
||||
lon = params.lon,
|
||||
);
|
||||
app.opener()
|
||||
.open_url(url, None::<&str>)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_changelog_url(app: AppHandle) -> Result<(), String> {
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
app.opener()
|
||||
.open_url(
|
||||
"https://github.com/JezzWTF/phokus/blob/main/CHANGELOG.md",
|
||||
None::<&str>,
|
||||
)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Database maintenance
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user