Files
decePubClient/Components/Nerd/NerdRow.cs
T
thepraandClaude Opus 5.5 3827d903a3
Build / Build (push) Successful in 33s
House rules: numbers through one formatter, no made-up server errors, and the rules fail the build
- Every number shown on a page goes through NumbersExtensions: counts,
  percentages, the nerd readouts and the values written into a style.
- A failure the client builds itself (offline, timeout, retries run out, an
  answer it can't read) is a 400 that names its kind; only a server's own 5XX
  passes through.
- Login, sign-up, recovery and password confirmations leave autocomplete to
  the browser, so it can offer the saved login.
- The Daisy components start with empty lists and attributes, and a
  decorative image still renders alt="".
- Arasaka.HouseRules.Analyzers checks all of this in every build, together
  with PrivaPub.ClientModels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4YtEJrgxG12Pc39Fai8Bd
2026-11-10 01:26:39 +01:00

60 lines
2.3 KiB
C#

using System.Globalization;
using decePubClient.Extensions;
namespace decePubClient.Components.Nerd;
/// <summary>One line of a NerdTip; a row without a value is not shown.</summary>
public sealed record NerdRow(string Label, string Value)
{
public static NerdRow Of(string label, object value) => new(label, value switch
{
null => null,
DateTime time => Time(time),
double number => number.ToReadout(),
bool flag => flag ? "yes" : "no",
IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),
_ => value.ToString()
});
/// <summary>A UTC time to the millisecond, as PrivaPub records it.</summary>
public static string Time(DateTime time) =>
DateTime.SpecifyKind(time, time.Kind == DateTimeKind.Unspecified ? DateTimeKind.Utc : time.Kind).ToUniversalTime()
.ToString("yyyy-MM-dd HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture);
/// <summary>A duration the way a person reads it: 850 ms, 12.4 s, 3 min 5 s, 2 h 10 min, 4 d 3 h.</summary>
public static string Duration(TimeSpan span)
{
var sign = span < TimeSpan.Zero ? "-" : string.Empty;
span = span.Duration();
return sign + (span.TotalSeconds switch
{
< 1 => $"{span.TotalMilliseconds.ToFixed(0)} ms",
< 60 => $"{span.TotalSeconds.ToFixed(1)} s",
< 3600 => $"{span.Minutes} min {span.Seconds} s",
< 86400 => $"{(int)span.TotalHours} h {span.Minutes} min",
_ => $"{(int)span.TotalDays} d {span.Hours} h"
});
}
/// <summary>A byte count: 912 B, 14.2 KiB, 3.1 MiB.</summary>
public static string Bytes(long? bytes) => bytes switch
{
null => null,
< 1024 => $"{bytes} B",
< 1024 * 1024 => $"{(bytes.Value / 1024d).ToFixed(1)} KiB",
_ => $"{(bytes.Value / 1048576d).ToFixed(1)} MiB"
};
/// <summary>The great-circle distance between two points in kilometres (haversine, Earth radius 6371 km).</summary>
public static double Kilometres(double latitude1, double longitude1, double latitude2, double longitude2)
{
const double radius = 6371;
double Radians(double degrees) => degrees * Math.PI / 180;
var dLatitude = Radians(latitude2 - latitude1);
var dLongitude = Radians(longitude2 - longitude1);
var a = Math.Pow(Math.Sin(dLatitude / 2), 2) + Math.Cos(Radians(latitude1)) * Math.Cos(Radians(latitude2)) * Math.Pow(Math.Sin(dLongitude / 2), 2);
return 2 * radius * Math.Asin(Math.Min(1, Math.Sqrt(a)));
}
}