init
This commit is contained in:
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
13
.idea/.idea.openF1Manager/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.openF1Manager/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/.idea.openF1Manager.iml
|
||||
/contentModel.xml
|
||||
/modules.xml
|
||||
/projectSettingsUpdater.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
1
.idea/.idea.openF1Manager/.idea/.name
generated
Normal file
1
.idea/.idea.openF1Manager/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
openF1Manager
|
||||
4
.idea/.idea.openF1Manager/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.openF1Manager/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
.idea/.idea.openF1Manager/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.openF1Manager/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/.idea.openF1Manager/.idea/libraries/GdSdk.xml
generated
Normal file
9
.idea/.idea.openF1Manager/.idea/libraries/GdSdk.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="GdSdk" type="GdScript">
|
||||
<CLASSES />
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="file://$APPLICATION_HOME_DIR$/plugins/rider-gdscript/sdk/extracted/4.4.1" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
6
.idea/.idea.openF1Manager/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.openF1Manager/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
1
icon.svg
Normal file
1
icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
|
After Width: | Height: | Size: 994 B |
37
icon.svg.import
Normal file
37
icon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://coxw1m7qyncsl"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
6
openF1Manager.csproj
Normal file
6
openF1Manager.csproj
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.4.1">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
19
openF1Manager.sln
Normal file
19
openF1Manager.sln
Normal file
@@ -0,0 +1,19 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "openF1Manager", "openF1Manager.csproj", "{096449E2-30A1-4ABB-991E-9CACF26B6297}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{096449E2-30A1-4ABB-991E-9CACF26B6297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{096449E2-30A1-4ABB-991E-9CACF26B6297}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{096449E2-30A1-4ABB-991E-9CACF26B6297}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{096449E2-30A1-4ABB-991E-9CACF26B6297}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{096449E2-30A1-4ABB-991E-9CACF26B6297}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{096449E2-30A1-4ABB-991E-9CACF26B6297}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
3
openF1Manager.sln.DotSettings.user
Normal file
3
openF1Manager.sln.DotSettings.user
Normal file
@@ -0,0 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F15d94cc257c9a3135f7a50c460c54531a6b6093993af2b851247763ed452e_003FHttpClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpRequest_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F116caee92f3114a1d347613bb5b2def20ee82c1e35fc2c7c7daf79cf349e319_003FHttpRequest_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
25
project.godot
Normal file
25
project.godot
Normal file
@@ -0,0 +1,25 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="openF1Manager"
|
||||
run/main_scene="uid://cx2vkr0hbfe4c"
|
||||
config/features=PackedStringArray("4.4", "C#", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="openF1Manager"
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
132
scenes/Main/Main.cs
Normal file
132
scenes/Main/Main.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Godot;
|
||||
|
||||
namespace openF1Manager.scenes.Main;
|
||||
|
||||
public partial class Main : Node
|
||||
{
|
||||
public enum APIEndpoints
|
||||
{
|
||||
CarData,
|
||||
Drivers,
|
||||
Intervals,
|
||||
Laps,
|
||||
Location,
|
||||
Meetings,
|
||||
Overtakes,
|
||||
Pit,
|
||||
Position,
|
||||
RaceControl,
|
||||
Sessions,
|
||||
SessionResult,
|
||||
StartingGrid,
|
||||
Stints,
|
||||
TeamRadio,
|
||||
Weather
|
||||
}
|
||||
|
||||
private static readonly Dictionary<APIEndpoints, string> enpointUrl = new()
|
||||
{
|
||||
{ APIEndpoints.CarData, "car_data" },
|
||||
{ APIEndpoints.Drivers, "drivers" },
|
||||
{ APIEndpoints.Intervals, "intervals" },
|
||||
{ APIEndpoints.Laps, "laps" },
|
||||
{ APIEndpoints.Location, "location" },
|
||||
{ APIEndpoints.Meetings, "meetings" },
|
||||
{ APIEndpoints.Overtakes, "overtakes" },
|
||||
{ APIEndpoints.Pit, "pit" },
|
||||
{ APIEndpoints.Position, "position" },
|
||||
{ APIEndpoints.RaceControl, "race_control" },
|
||||
{ APIEndpoints.Sessions, "sessions" },
|
||||
{ APIEndpoints.SessionResult, "session_result" },
|
||||
{ APIEndpoints.StartingGrid, "starting_grid" },
|
||||
{ APIEndpoints.Stints, "stints" },
|
||||
{ APIEndpoints.TeamRadio, "team_radio" },
|
||||
{ APIEndpoints.Weather, "weather" },
|
||||
};
|
||||
|
||||
[Export] public APIEndpoints Enpoint = APIEndpoints.Sessions;
|
||||
private string openf1Url = "https://api.openf1.org/v1/";
|
||||
|
||||
[Export] public string options = "date_start>=2023-09-01&date_end<=2023-09-30";
|
||||
|
||||
public override async void _Ready()
|
||||
{
|
||||
HttpRequest httpRequest = GetNode<HttpRequest>("HTTPRequest");
|
||||
// httpRequest.RequestCompleted += OnRequestCompleted;
|
||||
|
||||
// Created address
|
||||
GD.Print("Trying created address");
|
||||
string requestUrl = $"{openf1Url}{enpointUrl[Enpoint]}?{options}";
|
||||
GD.Print(requestUrl);
|
||||
//var error = httpRequest.Request(requestUrl);
|
||||
|
||||
|
||||
// await ToSignal(httpRequest, "request_completed");
|
||||
|
||||
// // Correct address
|
||||
// GD.Print("Trying hardcoded address");
|
||||
// requestUrl = "https://api.openf1.org/v1/sessions?date_start%3E%3D2023-09-01&date_end%3C%3D2023-09-30";
|
||||
// GD.Print("https://api.openf1.org/v1/sessions?date_start%3E%3D2023-09-01&date_end%3C%3D2023-09-30");
|
||||
// error = httpRequest.Request(requestUrl);
|
||||
// if (error != Error.Ok)
|
||||
// {
|
||||
// GD.Print(error);
|
||||
// return;
|
||||
// }
|
||||
|
||||
var children = GetChildren();
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (child is Control control)
|
||||
{
|
||||
GD.Print(control.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRequestCompleted(long result, long responseCode, string[] headers, byte[] body)
|
||||
{
|
||||
var text = Encoding.UTF8.GetString(body);
|
||||
var parsed = Json.ParseString(text);
|
||||
|
||||
if (parsed.VariantType == Variant.Type.Array)
|
||||
{
|
||||
var arr = parsed.AsGodotArray();
|
||||
GD.Print($"Array items: {arr.Count}");
|
||||
foreach (var item in arr)
|
||||
{
|
||||
// Each item is typically an object/dictionary; print a few fields or all key-values
|
||||
if (item.VariantType == Variant.Type.Dictionary)
|
||||
{
|
||||
var dict = item.AsGodotDictionary();
|
||||
foreach (var (k, v) in dict)
|
||||
{
|
||||
GD.Print($"{k}: {v}");
|
||||
}
|
||||
GD.Print("----");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (parsed.VariantType == Variant.Type.Dictionary)
|
||||
{
|
||||
var json = parsed.AsGodotDictionary();
|
||||
GD.Print($"Object keys: {json.Count}");
|
||||
foreach (var (key, value) in json)
|
||||
{
|
||||
GD.Print($"{key}: {value}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PrintErr($"Unexpected JSON root type: {parsed.VariantType}");
|
||||
GD.Print(text); // Optional: inspect raw payload
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scenes/Main/Main.cs.uid
Normal file
1
scenes/Main/Main.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cj1o6v4tq5tiq
|
||||
55
scenes/Main/main.tscn
Normal file
55
scenes/Main/main.tscn
Normal file
@@ -0,0 +1,55 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cx2vkr0hbfe4c"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cj1o6v4tq5tiq" path="res://scenes/Main/Main.cs" id="1_gol0p"]
|
||||
[ext_resource type="PackedScene" uid="uid://icuiqfoy5pkr" path="res://scenes/Tools/session_tool.tscn" id="2_br8qb"]
|
||||
|
||||
[node name="Main" type="Node"]
|
||||
script = ExtResource("1_gol0p")
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="."]
|
||||
|
||||
[node name="HUD" type="MarginContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
|
||||
[node name="SessionTool" parent="HUD" instance=ExtResource("2_br8qb")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GlobalTool" type="MarginContainer" parent="HUD"]
|
||||
clip_contents = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
|
||||
[node name="BoxContainer" type="BoxContainer" parent="HUD/GlobalTool"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="HUD/GlobalTool/BoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="HUD/GlobalTool/BoxContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="HUD/GlobalTool/BoxContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "openF1 Manager"
|
||||
|
||||
[node name="Button" type="Button" parent="HUD/GlobalTool/BoxContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Send Request"
|
||||
|
||||
[node name="ItemList" type="ItemList" parent="HUD/GlobalTool/BoxContainer/VBoxContainer"]
|
||||
clip_contents = false
|
||||
custom_minimum_size = Vector2(150, 20)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
max_text_lines = 3
|
||||
auto_width = true
|
||||
text_overrun_behavior = 2
|
||||
item_count = 1
|
||||
item_0/text = "None"
|
||||
154
scenes/Tools/SessionTool.cs
Normal file
154
scenes/Tools/SessionTool.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System.Text;
|
||||
using Godot;
|
||||
using openF1Manager.scripts;
|
||||
|
||||
namespace openF1Manager.scenes.Main;
|
||||
|
||||
public partial class SessionTool : MarginContainer
|
||||
{
|
||||
private LineEdit _meetingKey;
|
||||
private LineEdit _sessionKey;
|
||||
private LineEdit _location;
|
||||
private LineEdit _dateStart;
|
||||
private LineEdit _dateEnd;
|
||||
|
||||
private string _baseAddress = "https://api.openf1.org/v1/sessions?";
|
||||
|
||||
// MVVM-lite
|
||||
private SessionFilter _vm;
|
||||
private FormBinder<SessionFilter> _binder;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_meetingKey =
|
||||
GetNode<LineEdit>(
|
||||
"ElementsVBoxContainer/PropertiesVBoxContainer/MeetingKeyHBoxContainer/MeetingKeyLineEdit");
|
||||
_sessionKey =
|
||||
GetNode<LineEdit>(
|
||||
"ElementsVBoxContainer/PropertiesVBoxContainer/SessionKeyHBoxContainer/SessionKeyLineEdit");
|
||||
_location = GetNode<LineEdit>(
|
||||
"ElementsVBoxContainer/PropertiesVBoxContainer/LocationHBoxContainer/LocationLineEdit");
|
||||
_dateStart =
|
||||
GetNode<LineEdit>("ElementsVBoxContainer/PropertiesVBoxContainer/DateStartHBoxContainer/DateStartLineEdit");
|
||||
_dateEnd = GetNode<LineEdit>(
|
||||
"ElementsVBoxContainer/PropertiesVBoxContainer/DateEndHBoxContainer/DateEndLineEdit");
|
||||
|
||||
GD.Print(_meetingKey.PlaceholderText);
|
||||
|
||||
// Create VM and bind all LineEdits under the form root
|
||||
_vm = new SessionFilter();
|
||||
var formRoot = GetNode<Control>("ElementsVBoxContainer/PropertiesVBoxContainer");
|
||||
_binder = new FormBinder<SessionFilter>(_vm, this).Bind();
|
||||
}
|
||||
|
||||
|
||||
public string BuildQueryOld()
|
||||
{
|
||||
GD.Print("Building query");
|
||||
var queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);
|
||||
|
||||
foreach (var pair in new (string Key, string Value)[]
|
||||
{
|
||||
("meeting_key", _meetingKey.Text),
|
||||
("session_key", _sessionKey.Text),
|
||||
("location", _location.Text),
|
||||
("date_start", _dateStart.Text),
|
||||
("date_end", _dateEnd.Text),
|
||||
})
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(pair.Value))
|
||||
queryString[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
return _baseAddress + queryString.ToString();
|
||||
}
|
||||
|
||||
public string BuildQuery()
|
||||
{
|
||||
GD.Print("Building query");
|
||||
return ApiModelSerializer.ToQueryString(_vm, _baseAddress);
|
||||
}
|
||||
|
||||
public async void SendRequest()
|
||||
{
|
||||
string requestAddress = BuildQuery();
|
||||
GD.Print(requestAddress);
|
||||
var httpRequest = GetParent().GetParent().GetNode<HttpRequest>("HTTPRequest");
|
||||
httpRequest.RequestCompleted += OnSessionRequestCompleted;
|
||||
|
||||
GD.Print("Sending request");
|
||||
var error = httpRequest.Request(requestAddress);
|
||||
|
||||
if (error != Error.Ok)
|
||||
{
|
||||
GD.Print(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSessionRequestCompleted(long result, long responseCode, string[] headers, byte[] body)
|
||||
{
|
||||
GD.Print("Request completed");
|
||||
var text = Encoding.UTF8.GetString(body);
|
||||
var parsed = Json.ParseString(text);
|
||||
|
||||
if (parsed.VariantType != Variant.Type.Array)
|
||||
{
|
||||
GD.PrintErr("Unexpected JSON format. Expected array.");
|
||||
return;
|
||||
}
|
||||
|
||||
var arr = parsed.AsGodotArray();
|
||||
if (arr.Count == 0)
|
||||
{
|
||||
GD.Print("No sessions returned.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate List of sessions
|
||||
ItemList itemList = GetNode<ItemList>("ElementsVBoxContainer/ControlsVBoxContainer/ItemList");
|
||||
itemList.Clear();
|
||||
|
||||
int count = 0;
|
||||
foreach (var item in arr)
|
||||
{
|
||||
count++;
|
||||
itemList.AddItem(count.ToString());
|
||||
}
|
||||
|
||||
// Populate UI fields from first item in array
|
||||
var first = arr[0];
|
||||
if (first.VariantType != Variant.Type.Dictionary)
|
||||
{
|
||||
GD.PrintErr("Unexpected item format. Expected dictionary.");
|
||||
return;
|
||||
}
|
||||
|
||||
var dict = first.AsGodotDictionary();
|
||||
|
||||
// Populate VM; binder reflects it to UI fields
|
||||
ApiModelSerializer.PopulateFromDictionary(_vm, dict);
|
||||
}
|
||||
|
||||
private void PopulateFieldsFromDictionary(Godot.Collections.Dictionary dict)
|
||||
{
|
||||
// Helper to read a value with multiple possible key casings
|
||||
string GetString(params string[] keys)
|
||||
{
|
||||
foreach (var k in keys)
|
||||
{
|
||||
if (dict.ContainsKey(k) && dict[k].VariantType != Variant.Type.Nil)
|
||||
return dict[k].ToString();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Map likely API keys; try snake_case first, then camelCase fallbacks
|
||||
_meetingKey.Text = GetString("meeting_key", "meetingKey");
|
||||
_sessionKey.Text = GetString("session_key", "sessionKey");
|
||||
_location.Text = GetString("location", "Location");
|
||||
_dateStart.Text = GetString("date_start", "dateStart", "DateStart");
|
||||
_dateEnd.Text = GetString("date_end", "dateEnd", "DateEnd");
|
||||
}
|
||||
|
||||
}
|
||||
1
scenes/Tools/SessionTool.cs.uid
Normal file
1
scenes/Tools/SessionTool.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ccc1qonkjw7k8
|
||||
222
scenes/Tools/session_tool.tscn
Normal file
222
scenes/Tools/session_tool.tscn
Normal file
@@ -0,0 +1,222 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://icuiqfoy5pkr"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ccc1qonkjw7k8" path="res://scenes/Tools/SessionTool.cs" id="1_icata"]
|
||||
|
||||
[sub_resource type="SystemFont" id="SystemFont_br8qb"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_px18m"]
|
||||
default_font = SubResource("SystemFont_br8qb")
|
||||
|
||||
[node name="SessionTool" type="MarginContainer"]
|
||||
clip_contents = true
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
theme = SubResource("Theme_px18m")
|
||||
script = ExtResource("1_icata")
|
||||
|
||||
[node name="ElementsBoxContainer" type="BoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
vertical = true
|
||||
|
||||
[node name="PropertiesVBoxContainer" type="VBoxContainer" parent="ElementsBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 1
|
||||
|
||||
[node name="MeetingKeyHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MeetingKeyLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/MeetingKeyHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Meeting Key:"
|
||||
|
||||
[node name="MeetingKeyLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/MeetingKeyHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "latest"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="SessionKeyHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SessionKeyLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/SessionKeyHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Session Key: "
|
||||
|
||||
[node name="SessionKeyLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/SessionKeyHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "latest"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="LocationHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LocationLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/LocationHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Location:"
|
||||
|
||||
[node name="LocationLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/LocationHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "Monza"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="DateStartHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DateStartLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/DateStartHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Date Start:"
|
||||
|
||||
[node name="DateStartLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/DateStartHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "2023-09-01T11:30:00+00:00"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="DateEndHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DateEndLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/DateEndHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Date End:"
|
||||
|
||||
[node name="DateEndLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/DateEndHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "2023-09-01T11:30:00+00:00"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="SessionTypeHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SessionTypeLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/SessionTypeHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Session Type:"
|
||||
|
||||
[node name="SessionTypeLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/SessionTypeHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "Practice"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="SessionNameHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SessionNameLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/SessionNameHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Session Name:"
|
||||
|
||||
[node name="SessionNameLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/SessionNameHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "Practice 1"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="CountryKeyHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CountryKeyLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/CountryKeyHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Country Key:"
|
||||
|
||||
[node name="CountryKeyLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/CountryKeyHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "13.0"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="CountryCodeHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CountryCodeLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/CountryCodeHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Country Code:"
|
||||
|
||||
[node name="CountryCodeLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/CountryCodeHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "ITA"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="CountryNameHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CountryNameLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/CountryNameHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Country Name:"
|
||||
|
||||
[node name="CountryNameLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/CountryNameHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "Italy"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="CircuitKeyHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CircuitKeyLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/CircuitKeyHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Circuit Key:"
|
||||
|
||||
[node name="CircuitKeyLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/CircuitKeyHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "39.0"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="CircuitShortNameHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CircuitShortNameLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/CircuitShortNameHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Circuit Short Name:"
|
||||
|
||||
[node name="CircuitShortNameLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/CircuitShortNameHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "Monza"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="GmtOffsetHBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GmtOffsetLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/GmtOffsetHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "GMT Offset:"
|
||||
|
||||
[node name="GmtOffsetLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/GmtOffsetHBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "02:00:00"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="YearBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer/PropertiesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="YearLabel" type="Label" parent="ElementsBoxContainer/PropertiesVBoxContainer/YearBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Year:"
|
||||
|
||||
[node name="YearLineEdit" type="LineEdit" parent="ElementsBoxContainer/PropertiesVBoxContainer/YearBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "2023.0"
|
||||
expand_to_text_length = true
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="ControlsVBoxContainer" type="HBoxContainer" parent="ElementsBoxContainer"]
|
||||
layout_mode = 2
|
||||
95
scripts/ApiModelSerializer.cs
Normal file
95
scripts/ApiModelSerializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
1
scripts/ApiModelSerializer.cs.uid
Normal file
1
scripts/ApiModelSerializer.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://kos10xtghum5
|
||||
88
scripts/FormBinder.cs
Normal file
88
scripts/FormBinder.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
1
scripts/FormBinder.cs.uid
Normal file
1
scripts/FormBinder.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://4puwe430cmav
|
||||
154
scripts/SessionFilter.cs
Normal file
154
scripts/SessionFilter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
1
scripts/SessionFilter.cs.uid
Normal file
1
scripts/SessionFilter.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://db7e6qcg77eky
|
||||
Reference in New Issue
Block a user