This commit is contained in:
gdz
2025-09-07 14:57:10 +02:00
commit 7770ce1ae3
27 changed files with 1046 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Godot;
using Godot.Collections;
using openF1Manager.scripts;
public static class ApiModelSerializer
{
public static string ToQueryString(object model, string baseAddress)
{
var query = System.Web.HttpUtility.ParseQueryString(string.Empty);
foreach (var (key, value) in EnumerateKeyValues(model))
{
if (!string.IsNullOrWhiteSpace(value))
query[key] = value;
}
var queryString = query.ToString();
return string.IsNullOrEmpty(queryString) ? baseAddress : $"{baseAddress}?{queryString}";
}
public static void PopulateFromDictionary(object model, Godot.Collections.Dictionary dict)
{
var type = model.GetType();
foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!prop.CanWrite) continue;
var attr = prop.GetCustomAttribute<ApiFieldAttribute>();
var key = attr?.Key ?? ToSnakeCase(prop.Name);
// Try snake_case first, then camelCase fallbacks
if (!TryGet(dict, key, out var str) && !TryGet(dict, SnakeToCamel(key), out str))
continue;
// You can add type conversions if needed; here we only set strings
if (prop.PropertyType == typeof(string) || prop.PropertyType == typeof(DateTime) || prop.PropertyType == typeof(int) || prop.PropertyType == typeof(float))
prop.SetValue(model, str);
}
}
private static bool TryGet(Dictionary dict, string key, out string value)
{
if (dict.ContainsKey(key) && dict[key].VariantType != Variant.Type.Nil)
{
value = dict[key].ToString();
return true;
}
value = string.Empty;
return false;
}
private static IEnumerable<(string Key, string Value)> EnumerateKeyValues(object model)
{
var type = model.GetType();
foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!prop.CanRead) continue;
var value = prop.GetValue(model)?.ToString();
var attr = prop.GetCustomAttribute<ApiFieldAttribute>();
var key = attr?.Key ?? ToSnakeCase(prop.Name);
yield return (key, value);
}
}
private static string ToSnakeCase(string name)
{
var sb = new StringBuilder();
for (int i = 0; i < name.Length; i++)
{
var c = name[i];
sb.Append(i > 0 && char.IsUpper(c) ? "_" + char.ToLower(c) : c.ToString());
}
return sb.ToString();
}
private static string SnakeToCamel(string name)
{
var sb = new StringBuilder();
sb.Append(char.ToLower(name[0]));
for (int i = 1; i < name.Length; i++)
{
var c = name[i];
sb.Append(char.IsUpper(c) ? char.ToLower(c) : c);
}
return sb.ToString();
}
}

View File

@@ -0,0 +1 @@
uid://kos10xtghum5

88
scripts/FormBinder.cs Normal file
View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using Godot;
public sealed class FormBinder<TViewModel> where TViewModel : INotifyPropertyChanged
{
private readonly TViewModel _vm;
private readonly Control _root;
private readonly Dictionary<LineEdit, PropertyInfo> _lineToProp = new();
private readonly Dictionary<string, LineEdit> _propNameToLine = new(StringComparer.OrdinalIgnoreCase);
private bool _suppress;
public FormBinder(TViewModel vm, Control root)
{
_vm = vm;
_root = root;
}
public FormBinder<TViewModel> Bind()
{
Discover();
_vm.PropertyChanged += OnPropertyChanged;
foreach (var (line, prop) in _lineToProp)
{
line.TextChanged += (string text) =>
{
if (_suppress) return;
if (prop.PropertyType == typeof(string) || prop.PropertyType == typeof(string))
prop.SetValue(_vm, text);
};
}
// Initial sync UI <- VM
PushAllFromVm();
return this;
}
private void Discover()
{
var props = typeof(TViewModel).GetProperties(BindingFlags.Instance | BindingFlags.Public);
var propMap = new Dictionary<string, PropertyInfo>(StringComparer.OrdinalIgnoreCase);
foreach (var p in props) if (p.CanRead || p.CanWrite) propMap[p.Name] = p;
void Walk(Node n)
{
if (n is LineEdit le)
{
// Prefer explicit metadata "vm_prop" to map to a property name; fallback to node name.
string propName = le.HasMeta("vm_prop") ? (string)le.GetMeta("vm_prop") : StripSuffix(le.Name, "LineEdit");
if (propMap.TryGetValue(propName, out var pi))
{
_lineToProp[le] = pi;
_propNameToLine[propName] = le;
}
}
foreach (var c in n.GetChildren())
if (c is Node child) Walk(child);
}
Walk(_root);
}
private static string StripSuffix(string s, string suffix) =>
s.EndsWith(suffix, StringComparison.Ordinal) ? s[..^suffix.Length] : s;
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == null) return;
if (_propNameToLine.TryGetValue(e.PropertyName, out var le))
{
_suppress = true;
try { le.Text = _lineToProp[le].GetValue(_vm)?.ToString() ?? string.Empty; }
finally { _suppress = false; }
}
}
public void PushAllFromVm()
{
_suppress = true;
try
{
foreach (var (line, prop) in _lineToProp)
line.Text = prop.GetValue(_vm)?.ToString() ?? string.Empty;
}
finally { _suppress = false; }
}
}

View File

@@ -0,0 +1 @@
uid://4puwe430cmav

154
scripts/SessionFilter.cs Normal file
View File

@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace openF1Manager.scripts;
/// <summary>
/// An attribute that is applied to a property to associate it with a specific API field key.
/// </summary>
/// <remarks>
/// This attribute is used to map a class property to a corresponding field in the API.
/// It allows specifying a key that identifies the corresponding API field.
/// </remarks>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ApiFieldAttribute : Attribute
{
public string Key { get; }
public ApiFieldAttribute(string key) => Key = key;
}
public partial class SessionFilter : INotifyPropertyChanged
{
private string? _meetingKey;
private string? _sessionKey;
private string? _location;
private DateTime? _dateStart;
private DateTime? _dateEnd;
private string? _sessionType;
private string? _sessionName;
private int? _countryKey;
private string? _countryCode;
private string? _countryName;
private int? _circuitKey;
private string? _circuitShortName;
private string? _gmtOffset;
private string? _year;
[ApiField("meeting_key")]
public string MeetingKey
{
get => _meetingKey;
set => SetField(ref _meetingKey, value);
}
[ApiField("session_key")]
public string SessionKey
{
get => _sessionKey;
set => SetField(ref _sessionKey, value);
}
[ApiField("location")]
public string Location
{
get => _location;
set => SetField(ref _location, value);
}
[ApiField("date_start")]
public DateTime? DateStart
{
get => _dateStart;
set => SetField(ref _dateStart, value);
}
[ApiField("date_en")]
public DateTime? DateEnd
{
get => _dateEnd;
set => SetField(ref _dateEnd, value);
}
[ApiField("session_type")]
public string SessionType
{
get => _sessionType;
set => SetField(ref _sessionType, value);
}
[ApiField("session_name")]
public string SessionName
{
get => _sessionName;
set => SetField(ref _sessionName, value);
}
[ApiField("country_key")]
public int? CountryKey
{
get => _countryKey;
set => SetField(ref _countryKey, value);
}
[ApiField("country_code")]
public string CountryCode
{
get => _countryCode;
set => SetField(ref _countryCode, value);
}
[ApiField("country_name")]
public string CountryName
{
get => _countryName;
set => SetField(ref _countryName, value);
}
[ApiField("circuit_key")]
public int? CircuitKey
{
get => _circuitKey;
set => SetField(ref _circuitKey, value);
}
[ApiField("circuit_short_name")]
public string CircuitShortName
{
get => _circuitShortName;
set => SetField(ref _circuitShortName, value);
}
[ApiField("gmt_offset")]
public string GmtOffset
{
get => _gmtOffset;
set => SetField(ref _gmtOffset, value);
}
[ApiField("year")]
public string Year
{
get => _year;
set => SetField(ref _year, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}

View File

@@ -0,0 +1 @@
uid://db7e6qcg77eky