From 71896fec2459d1e860b8d785840184bbb7a3eccd Mon Sep 17 00:00:00 2001 From: nopoz Date: Fri, 21 Aug 2026 12:21:48 -0700 Subject: [PATCH] Add TemperatureDecimalDigits setting to control temperature precision Replaces the hardcoded single decimal place in the weather display with a configurable TemperatureDecimalDigits setting accepting 0-2. Defaults to 1, which preserves the current output. Both config formats reject values outside 0-2. The V1 adapter validates too, since it exposes the setting from the legacy config and an out-of-range value would otherwise reach toFixed() in the browser, which throws a RangeError below 0 or above 100. The openapi spec is updated so the generated web client stays in sync. --- .../Interfaces/IClientSettings.cs | 1 + ...TemperatureDecimalDigitsValidationTests.cs | 50 +++++++++++++++++++ .../Resources/TestV1.json | 1 + .../Resources/TestV2.json | 1 + ImmichFrame.WebApi.Tests/Resources/TestV2.yml | 1 + .../Helpers/Config/ServerSettingsV1.cs | 12 ++++- .../Models/ClientSettingsDto.cs | 1 + ImmichFrame.WebApi/Models/ServerSettings.cs | 11 +++- docker/Settings.example.json | 1 + docker/Settings.example.yml | 1 + docker/example.env | 1 + .../src/lib/components/elements/clock.svelte | 2 +- immichFrame.Web/src/lib/immichFrameApi.ts | 1 + openApi/swagger.json | 4 ++ 14 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 ImmichFrame.WebApi.Tests/Models/TemperatureDecimalDigitsValidationTests.cs diff --git a/ImmichFrame.Core/Interfaces/IClientSettings.cs b/ImmichFrame.Core/Interfaces/IClientSettings.cs index 576c13eb..948c3a2b 100644 --- a/ImmichFrame.Core/Interfaces/IClientSettings.cs +++ b/ImmichFrame.Core/Interfaces/IClientSettings.cs @@ -23,6 +23,7 @@ public interface IClientSettings public string Style { get; } public string? BaseFontSize { get; } public bool ShowWeatherDescription { get; } + public int TemperatureDecimalDigits { get; } public string? WeatherIconUrl { get; } public bool ImageZoom { get; } public bool ImagePan { get; } diff --git a/ImmichFrame.WebApi.Tests/Models/TemperatureDecimalDigitsValidationTests.cs b/ImmichFrame.WebApi.Tests/Models/TemperatureDecimalDigitsValidationTests.cs new file mode 100644 index 00000000..3cbfc209 --- /dev/null +++ b/ImmichFrame.WebApi.Tests/Models/TemperatureDecimalDigitsValidationTests.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; +using ImmichFrame.WebApi.Helpers; +using ImmichFrame.WebApi.Models; + +namespace ImmichFrame.WebApi.Tests.Models; + +[TestFixture] +public class TemperatureDecimalDigitsValidationTests +{ + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void GeneralSettings_AcceptsSupportedPrecision(int digits) + { + var settings = new GeneralSettings { TemperatureDecimalDigits = digits }; + + Assert.DoesNotThrow(() => settings.Validate()); + } + + [TestCase(-1)] + [TestCase(3)] + [TestCase(101)] + public void GeneralSettings_RejectsUnsupportedPrecision(int digits) + { + var settings = new GeneralSettings { TemperatureDecimalDigits = digits }; + + Assert.Throws(() => settings.Validate()); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void V1Config_AcceptsSupportedPrecision(int digits) + { + var v1 = new ServerSettingsV1 { TemperatureDecimalDigits = digits }; + + Assert.DoesNotThrow(() => new ServerSettingsV1Adapter(v1).GeneralSettings.Validate()); + } + + [TestCase(-1)] + [TestCase(101)] + public void V1Config_RejectsUnsupportedPrecision(int digits) + { + var v1 = new ServerSettingsV1 { TemperatureDecimalDigits = digits }; + + Assert.That(() => new ServerSettingsV1Adapter(v1).GeneralSettings.Validate(), + Throws.TypeOf(), + "a legacy config must not pass through a value that would throw RangeError in toFixed()"); + } +} diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV1.json b/ImmichFrame.WebApi.Tests/Resources/TestV1.json index e6c49102..e9d24fce 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV1.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV1.json @@ -51,6 +51,7 @@ "BaseFontSize": "BaseFontSize_TEST", "WeatherApiKey": "WeatherApiKey_TEST", "ShowWeatherDescription": true, + "TemperatureDecimalDigits": 7, "WeatherIconUrl": "WeatherIconUrl_TEST", "UnitSystem": "UnitSystem_TEST", "WeatherLatLong": "WeatherLatLong_TEST", diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2.json b/ImmichFrame.WebApi.Tests/Resources/TestV2.json index 4d603dc9..88611c64 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2.json @@ -30,6 +30,7 @@ "Style": "Style_TEST", "BaseFontSize": "BaseFontSize_TEST", "ShowWeatherDescription": true, + "TemperatureDecimalDigits": 7, "WeatherIconUrl": "WeatherIconUrl_TEST", "ImageZoom": true, "ImagePan": true, diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2.yml b/ImmichFrame.WebApi.Tests/Resources/TestV2.yml index 47f45947..bd252886 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2.yml +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2.yml @@ -29,6 +29,7 @@ General: Style: Style_TEST BaseFontSize: BaseFontSize_TEST ShowWeatherDescription: true + TemperatureDecimalDigits: 7 WeatherIconUrl: WeatherIconUrl_TEST ImageZoom: true ImagePan: true diff --git a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs index 076f36da..79e7fe3d 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs @@ -50,6 +50,7 @@ public class ServerSettingsV1 : IConfigSettable public string Style { get; set; } = "none"; public string? BaseFontSize { get; set; } public bool ShowWeatherDescription { get; set; } = true; + public int TemperatureDecimalDigits { get; set; } = 1; public string? WeatherIconUrl { get; set; } = "https://openweathermap.org/img/wn/{IconId}.png"; public bool ImageZoom { get; set; } = true; public bool ImagePan { get; set; } = false; @@ -128,6 +129,7 @@ class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings public string Style => _delegate.Style; public string? BaseFontSize => _delegate.BaseFontSize; public bool ShowWeatherDescription => _delegate.ShowWeatherDescription; + public int TemperatureDecimalDigits => _delegate.TemperatureDecimalDigits; public string? WeatherIconUrl => _delegate.WeatherIconUrl; public bool ImageZoom => _delegate.ImageZoom; public bool ImagePan => _delegate.ImagePan; @@ -136,6 +138,14 @@ class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings public string Layout => _delegate.Layout; public string Language => _delegate.Language; - public void Validate() { } + public void Validate() + { + if (TemperatureDecimalDigits < 0 || TemperatureDecimalDigits > 2) + { + throw new ArgumentOutOfRangeException(nameof(TemperatureDecimalDigits), + TemperatureDecimalDigits, + "TemperatureDecimalDigits must be between 0 and 2."); + } + } } } diff --git a/ImmichFrame.WebApi/Models/ClientSettingsDto.cs b/ImmichFrame.WebApi/Models/ClientSettingsDto.cs index 5e408fe9..0f782d50 100644 --- a/ImmichFrame.WebApi/Models/ClientSettingsDto.cs +++ b/ImmichFrame.WebApi/Models/ClientSettingsDto.cs @@ -25,6 +25,7 @@ public class ClientSettingsDto(IClientSettings settings) : IClientSettings public string Style => settings.Style; public string? BaseFontSize => settings.BaseFontSize; public bool ShowWeatherDescription => settings.ShowWeatherDescription; + public int TemperatureDecimalDigits => settings.TemperatureDecimalDigits; public string? WeatherIconUrl => settings.WeatherIconUrl; public bool ImageZoom => settings.ImageZoom; public bool ImagePan => settings.ImagePan; diff --git a/ImmichFrame.WebApi/Models/ServerSettings.cs b/ImmichFrame.WebApi/Models/ServerSettings.cs index 74d0fb8e..6e1e6217 100644 --- a/ImmichFrame.WebApi/Models/ServerSettings.cs +++ b/ImmichFrame.WebApi/Models/ServerSettings.cs @@ -58,6 +58,7 @@ public class GeneralSettings : IGeneralSettings, IConfigSettable public string Style { get; set; } = "none"; public string? BaseFontSize { get; set; } public bool ShowWeatherDescription { get; set; } = true; + public int TemperatureDecimalDigits { get; set; } = 1; public string? WeatherIconUrl { get; set; } = "https://openweathermap.org/img/wn/{IconId}.png"; public bool ImageZoom { get; set; } = true; public bool ImagePan { get; set; } = false; @@ -73,7 +74,15 @@ public class GeneralSettings : IGeneralSettings, IConfigSettable public string? Webhook { get; set; } public string? AuthenticationSecret { get; set; } - public void Validate() { } + public void Validate() + { + if (TemperatureDecimalDigits < 0 || TemperatureDecimalDigits > 2) + { + throw new ArgumentOutOfRangeException(nameof(TemperatureDecimalDigits), + TemperatureDecimalDigits, + "TemperatureDecimalDigits must be between 0 and 2."); + } + } } public class ServerAccountSettings : IAccountSettings, IConfigSettable diff --git a/docker/Settings.example.json b/docker/Settings.example.json index a86a4d00..a6ba1a32 100644 --- a/docker/Settings.example.json +++ b/docker/Settings.example.json @@ -30,6 +30,7 @@ "Style": "none", "BaseFontSize": "17px", "ShowWeatherDescription": true, + "TemperatureDecimalDigits": 1, "WeatherIconUrl": "https://openweathermap.org/img/wn/{IconId}.png", "ImageZoom": true, "ImagePan": false, diff --git a/docker/Settings.example.yml b/docker/Settings.example.yml index 173b31a5..942179e4 100644 --- a/docker/Settings.example.yml +++ b/docker/Settings.example.yml @@ -28,6 +28,7 @@ General: Style: none BaseFontSize: 17px ShowWeatherDescription: true + TemperatureDecimalDigits: 1 WeatherIconUrl: 'https://openweathermap.org/img/wn/{IconId}.png' ImageZoom: true ImagePan: false diff --git a/docker/example.env b/docker/example.env index 51d80ed5..a1beb3ff 100644 --- a/docker/example.env +++ b/docker/example.env @@ -43,6 +43,7 @@ ApiKey=KEY # BaseFontSize=17px # WeatherApiKey= # ShowWeatherDescription=true +# TemperatureDecimalDigits=1 # WeatherIconUrl=https://openweathermap.org/img/wn/{IconId}.png # UnitSystem=imperial # WeatherLatLong= diff --git a/immichFrame.Web/src/lib/components/elements/clock.svelte b/immichFrame.Web/src/lib/components/elements/clock.svelte index e4d00ec4..a290e708 100644 --- a/immichFrame.Web/src/lib/components/elements/clock.svelte +++ b/immichFrame.Web/src/lib/components/elements/clock.svelte @@ -90,7 +90,7 @@ {/if}
{weather.location},
-
{weather.temperature?.toFixed(1)}°
+
{weather.temperature?.toFixed($configStore.temperatureDecimalDigits ?? 1)}°
{#if $configStore.showWeatherDescription} diff --git a/immichFrame.Web/src/lib/immichFrameApi.ts b/immichFrame.Web/src/lib/immichFrameApi.ts index 85fa762b..94ac48d6 100644 --- a/immichFrame.Web/src/lib/immichFrameApi.ts +++ b/immichFrame.Web/src/lib/immichFrameApi.ts @@ -221,6 +221,7 @@ export type ClientSettingsDto = { style?: string | null; baseFontSize?: string | null; showWeatherDescription?: boolean; + temperatureDecimalDigits?: number; weatherIconUrl?: string | null; imageZoom?: boolean; imagePan?: boolean; diff --git a/openApi/swagger.json b/openApi/swagger.json index 9569c297..02f1f177 100644 --- a/openApi/swagger.json +++ b/openApi/swagger.json @@ -1069,6 +1069,10 @@ "showWeatherDescription": { "type": "boolean" }, + "temperatureDecimalDigits": { + "type": "integer", + "format": "int32" + }, "weatherIconUrl": { "type": "string", "nullable": true