feat(units): show distances in miles
Everything is computed in kilometres and converted once at display time, in lib/units — storing miles anywhere would give the same number two sources of truth and a rounding error that grows with every hop. The grids capture the unit inside their column definitions, header and formatter both, so a change is published to them the way the date format already is; without that a toggle would only appear after a language change or a restart. The preference is portable, like the other display ones.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// Distance units.
|
||||
//
|
||||
// Everything is COMPUTED in kilometres — the great-circle maths, the backend's
|
||||
// distance_km on a spot, the map's path lengths — and converted once, here, at
|
||||
// display time. Storing miles anywhere would mean two sources of truth for the
|
||||
// same number and a rounding error that grows with every hop.
|
||||
//
|
||||
// The preference is portable (see lib/uiPref): an operator who works in miles
|
||||
// works in miles on every machine they copy their folder to.
|
||||
import { writeUiPref } from '@/lib/uiPref';
|
||||
|
||||
export const KEY_MILES = 'opslog.distanceMiles';
|
||||
const KM_PER_MILE = 1.609344; // statute miles, the ones a US licence is used in
|
||||
|
||||
export function useMiles(): boolean {
|
||||
try { return localStorage.getItem(KEY_MILES) === '1'; } catch { return false; }
|
||||
}
|
||||
|
||||
export function setUseMiles(on: boolean): void {
|
||||
writeUiPref(KEY_MILES, on ? '1' : '0');
|
||||
listeners.forEach((l) => l());
|
||||
}
|
||||
|
||||
// subscribeDistanceUnit notifies on a change. The grids capture the unit inside
|
||||
// their column definitions (header text and formatter both), so without this a
|
||||
// toggle would only show up on the next language change or restart.
|
||||
const listeners = new Set<() => void>();
|
||||
export function subscribeDistanceUnit(fn: () => void): () => void {
|
||||
listeners.add(fn);
|
||||
return () => { listeners.delete(fn); };
|
||||
}
|
||||
|
||||
// distanceValue converts a distance in km to the operator's unit, rounded to a
|
||||
// whole unit — the precision the inputs actually justify (a 4-character grid is
|
||||
// a square tens of kilometres wide).
|
||||
export function distanceValue(km: number): number {
|
||||
if (!isFinite(km)) return 0;
|
||||
return Math.round(useMiles() ? km / KM_PER_MILE : km);
|
||||
}
|
||||
|
||||
// distanceUnit is the short label: "km" or "mi".
|
||||
export function distanceUnit(): string {
|
||||
return useMiles() ? 'mi' : 'km';
|
||||
}
|
||||
|
||||
// formatDistance is value + unit, thousands-separated: "12 345 km".
|
||||
export function formatDistance(km: number): string {
|
||||
return `${distanceValue(km).toLocaleString()} ${distanceUnit()}`;
|
||||
}
|
||||
Reference in New Issue
Block a user