diff --git a/src/chart/toChartData.ts b/src/chart/toChartData.ts index 95f4a5f4..1fe5a633 100644 --- a/src/chart/toChartData.ts +++ b/src/chart/toChartData.ts @@ -7,6 +7,7 @@ import { type GainReadings, } from '#model/PCA20035-solar/HistoryContext.js' import { xAxisForType } from '#chart/xAxisForType.js' +import { formatFloat } from '#utils/format.js' export const toChartData = ({ battery, @@ -35,7 +36,7 @@ export const toChartData = ({ subMilliseconds(base, base.getTime() - ts), ]), color: 'var(--color-nordic-sun)', - format: (v) => `${v.toFixed(1)} mA`, + format: (v) => `${formatFloat(v)} mA`, helperLines: [ { label: '1m', diff --git a/src/components/BME680.tsx b/src/components/BME680.tsx index 6a7b108d..d09997cb 100644 --- a/src/components/BME680.tsx +++ b/src/components/BME680.tsx @@ -1,6 +1,7 @@ import { LoadingIndicator } from '#components/ValueLoading.js' import { useDevice } from '#context/Device.js' import { isEnvironment, toEnvironment } from '#proto/lwm2m.js' +import { formatFloat } from '#utils/format.js' import { AngryIcon, AnnoyedIcon, @@ -57,12 +58,12 @@ export const EnvironmentReadings = () => { {c !== undefined && ( - {c.toFixed(1)} °C + {formatFloat(c)} °C )} {p !== undefined && ( - {p.toFixed(1)} % + {formatFloat(p)} % )} {mbar !== undefined && ( diff --git a/src/components/DeviceHeader.tsx b/src/components/DeviceHeader.tsx index 477adf1f..9c10f626 100644 --- a/src/components/DeviceHeader.tsx +++ b/src/components/DeviceHeader.tsx @@ -37,6 +37,8 @@ import { } from 'lucide-preact' import { useState } from 'preact/hooks' import './DeviceHeader.css' +import { CountryFlag } from './CountryFlag.js' +import { formatFloat } from '#utils/format.js' export const DeviceHeader = ({ device }: { device: Device }) => { const type = device.model @@ -156,7 +158,7 @@ const EnvironmentInfo = () => { {c === undefined && } {c !== undefined && ( - {c.toFixed(1)} °C + {formatFloat(c)} °C )} {iaq === undefined && } @@ -179,7 +181,7 @@ const EnvironmentInfo = () => { const NetworkModeInfo = () => { const { reported } = useDevice() - const { networkMode, currentBand, ts } = + const { networkMode, currentBand, mccmnc, ts } = Object.values(reported) .filter(isConnectionInformation) .map(toConnectionInformation)[0] ?? {} @@ -187,7 +189,7 @@ const NetworkModeInfo = () => { return ( - Network mode + Network {(networkMode === undefined || currentBand === undefined) && ( <> @@ -197,23 +199,26 @@ const NetworkModeInfo = () => { )} {networkMode !== undefined && currentBand !== undefined && ( - - {networkMode?.includes('LTE-M') ?? false ? ( - - ) : ( - - )} - + + + {networkMode?.includes('LTE-M') ?? false ? ( + + ) : ( + + )} + + {mccmnc !== undefined && } + )} {ts !== undefined && ( diff --git a/src/components/DeviceModeSelector.tsx b/src/components/DeviceModeSelector.tsx index 6de62ab2..184de1e6 100644 --- a/src/components/DeviceModeSelector.tsx +++ b/src/components/DeviceModeSelector.tsx @@ -9,6 +9,7 @@ import { Ban, HistoryIcon, Satellite, Settings2, X } from 'lucide-preact' import { useState } from 'preact/hooks' import { Problem } from './Problem.js' import { isEqual } from 'lodash-es' +import { formatFloat } from '#utils/format.js' export const DeviceModeSelector = ({ device, @@ -209,7 +210,8 @@ const DataUsageInfo = ({ > - This mode uses around {dataUsagePerDayMB.toFixed(2)} MB of data per day. + This mode uses around {formatFloat(dataUsagePerDayMB, 2)} MB of data per + day. ) diff --git a/src/map/LocationSourceLabels.tsx b/src/map/LocationSourceLabels.tsx index 0f3c3d43..54ac7c65 100644 --- a/src/map/LocationSourceLabels.tsx +++ b/src/map/LocationSourceLabels.tsx @@ -8,8 +8,8 @@ export enum LocationSource { // Uses nrfcloud.com wording export const LocationSourceLabels = new Map([ [LocationSource.WIFI, 'Wi-Fi'], - [LocationSource.MCELL, 'multi-cell'], - [LocationSource.SCELL, 'single-cell'], + [LocationSource.MCELL, 'Multi-cell'], + [LocationSource.SCELL, 'Single-cell'], [LocationSource.GNSS, 'GNSS'], ]) diff --git a/src/map/Map.tsx b/src/map/Map.tsx index ef124625..f9a95950 100644 --- a/src/map/Map.tsx +++ b/src/map/Map.tsx @@ -38,6 +38,7 @@ import { type GeoLocation, } from '#proto/lwm2m.js' import { useDeviceLocation, type Locations } from '#context/DeviceLocation.js' +import { formatFloat, formatInt } from '#utils/format.js' const trailColor = '#e169a5' const defaultColor = '#C7C7C7' @@ -530,11 +531,10 @@ export const Located = ({ location }: { location: GeoLocation }) => ( target="_blank" class="text-light" > - {location.lat.toFixed(5).replace(/0+$/, '')},{' '} - {location.lng.toFixed(5).replace(/0+$/, '')} + {formatFloat(location.lat, 5)}, {formatFloat(location.lng, 5)} {' '} {location.acc !== undefined ? ( - <>with an accuracy of {Math.round(location.acc)} m + <>with an accuracy of {formatInt(location.acc)} m ) : ( <>with an unspecified accuary )} diff --git a/src/model/PCA20035-solar/Chart.tsx b/src/model/PCA20035-solar/Chart.tsx index b2fe7cb6..aa024f6e 100644 --- a/src/model/PCA20035-solar/Chart.tsx +++ b/src/model/PCA20035-solar/Chart.tsx @@ -1,7 +1,7 @@ import { HistoryChart } from '#chart/HistoryChart.js' import { Ago } from '#components/Ago.js' import { LoadingIndicator } from '#components/ValueLoading.js' -import { formatFloat } from '#utils/formatFloat.js' +import { formatFloat } from '#utils/format.js' import { BatteryCharging, Sun } from 'lucide-preact' import { toChartData } from '#chart/toChartData.js' import { DateRangeButton } from '#chart/DateRangeButton.js' diff --git a/src/utils/format.spec.ts b/src/utils/format.spec.ts new file mode 100644 index 00000000..c124b0ee --- /dev/null +++ b/src/utils/format.spec.ts @@ -0,0 +1,20 @@ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { format } from './format.js' + +const { formatFloat, formatInt } = format('en-US') + +void describe('formatInt()', () => { + void it('should format an integer', () => { + assert.equal(formatInt(1234), '1,234') + }) +}) + +void describe('formatFloat()', () => { + void it('should format a float', () => { + assert.equal(formatFloat(1.234), '1.2') + }) + void it('should format a float with custom number of digits', () => { + assert.equal(formatFloat(1.234, 2), '1.23') + }) +}) diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 00000000..88a86608 --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,39 @@ +export const format = ( + locales?: Intl.LocalesArgument, +): { + formatInt: (value: number) => string + formatFloat: (value: number, maximumFractionDigits?: number) => string +} => { + const intFormatter = new Intl.NumberFormat(locales, { + maximumFractionDigits: 0, + }) + const formatInt = (value: number) => intFormatter.format(value) + + const formatters = new Map([ + [ + 1, + new Intl.NumberFormat(locales, { + maximumFractionDigits: 1, + }), + ], + ]) + + const formatFloat = (value: number, maximumFractionDigits = 1) => { + if (!formatters.has(maximumFractionDigits)) { + const f = new Intl.NumberFormat(undefined, { + maximumFractionDigits, + }) + formatters.set(maximumFractionDigits, f) + } + return formatters.get(maximumFractionDigits)!.format(value) + } + + return { + formatInt, + formatFloat, + } +} + +const { formatFloat, formatInt } = format() + +export { formatFloat, formatInt } diff --git a/src/utils/formatFloat.spec.ts b/src/utils/formatFloat.spec.ts deleted file mode 100644 index 134bf29d..00000000 --- a/src/utils/formatFloat.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { formatFloat } from './formatFloat.js' -import { describe, test as it } from 'node:test' -import assert from 'node:assert' - -void describe('formatFloat', () => { - void it('should nicely format floats', () => - assert.equal(formatFloat(4.762067631165049), '4.76')) - - void it('should cut off trailing slash', () => - assert.equal(formatFloat(4.0), '4')) -}) diff --git a/src/utils/formatFloat.ts b/src/utils/formatFloat.ts deleted file mode 100644 index 8a60c64b..00000000 --- a/src/utils/formatFloat.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const formatFloat = (f: number): string => - f.toFixed(2).replace(/\.0+$/, '')