feat: Add Game Client to Endpoints

This commit is contained in:
estrogen elf
2025-05-29 15:10:00 -05:00
parent c51f20a324
commit 1364cdc38c
15 changed files with 257 additions and 99 deletions

View File

@@ -43,7 +43,7 @@ namespace Needlework.Net
var lcuSchemaRaw = reader.Read(lcuSchemaStream, out var _); var lcuSchemaRaw = reader.Read(lcuSchemaStream, out var _);
_lcuSchemaDocument = new Document(lcuSchemaRaw); _lcuSchemaDocument = new Document(lcuSchemaRaw);
var lolClientStream = await _httpClient.GetStreamAsync("https://raw.githubusercontent.com/BlossomiShymae/poroschema/refs/heads/main/schemas/lolclient.json"); var lolClientStream = await _httpClient.GetStreamAsync("https://raw.githubusercontent.com/AlsoSylv/Irelia/refs/heads/master/schemas/game_schema.json");
var lolClientRaw = reader.Read(lolClientStream, out var _); var lolClientRaw = reader.Read(lolClientStream, out var _);
_lolClientDocument = new Document(lolClientRaw); _lolClientDocument = new Document(lolClientRaw);
} }

View File

@@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Needlework.Net.ViewModels.Shared; using Needlework.Net.ViewModels.Shared;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Needlework.Net.ViewModels.Pages; namespace Needlework.Net.ViewModels.Pages;
@@ -14,19 +15,19 @@ public partial class ConsoleViewModel : PageBase
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>(); public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
[ObservableProperty] private bool _isBusy = true; [ObservableProperty] private bool _isBusy = true;
[ObservableProperty] private LcuRequestViewModel _lcuRequest; [ObservableProperty] private RequestViewModel _request;
private readonly DataSource _dataSource; private readonly DataSource _dataSource;
public ConsoleViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, DataSource dataSource) : base("Console", "terminal", -200) public ConsoleViewModel(ILogger<RequestViewModel> requestViewModelLogger, DataSource dataSource, HttpClient httpClient) : base("Console", "terminal", -200)
{ {
_lcuRequest = new(lcuRequestViewModelLogger); _request = new(requestViewModelLogger, Endpoints.Tab.LCU, httpClient);
_dataSource = dataSource; _dataSource = dataSource;
} }
public override async Task InitializeAsync() public override async Task InitializeAsync()
{ {
var document = await _dataSource.GetLolClientDocumentAsync(); var document = await _dataSource.GetLcuSchemaDocumentAsync();
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() =>
{ {
RequestPaths.Clear(); RequestPaths.Clear();
@@ -39,6 +40,6 @@ public partial class ConsoleViewModel : PageBase
[RelayCommand] [RelayCommand]
private async Task SendRequest() private async Task SendRequest()
{ {
await LcuRequest.ExecuteAsync(); await Request.ExecuteAsync();
} }
} }

View File

@@ -21,10 +21,10 @@ public partial class EndpointViewModel : ObservableObject
public event EventHandler<string>? PathOperationSelected; public event EventHandler<string>? PathOperationSelected;
public EndpointViewModel(string endpoint, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Models.Document lcuSchemaDocument) public EndpointViewModel(string endpoint, ILogger<RequestViewModel> requestViewModelLogger, Models.Document document, Tab tab, System.Net.Http.HttpClient httpClient)
{ {
Endpoint = endpoint; Endpoint = endpoint;
PathOperations = new AvaloniaList<PathOperationViewModel>(lcuSchemaDocument.Plugins[endpoint].Select(x => new PathOperationViewModel(x, lcuRequestViewModelLogger, lcuSchemaDocument))); PathOperations = new AvaloniaList<PathOperationViewModel>(document.Plugins[endpoint].Select(x => new PathOperationViewModel(x, requestViewModelLogger, document, tab, httpClient)));
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations); FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
} }

View File

@@ -13,14 +13,27 @@ public partial class EndpointsNavigationViewModel : ObservableObject
[ObservableProperty] private ObservableObject _activeViewModel; [ObservableProperty] private ObservableObject _activeViewModel;
[ObservableProperty] private ObservableObject _endpointsViewModel; [ObservableProperty] private ObservableObject _endpointsViewModel;
[ObservableProperty] private string _title = string.Empty; [ObservableProperty] private string _title;
private readonly Action<string?, Guid> _onEndpointNavigation; private readonly Action<string?, Guid> _onEndpointNavigation;
private readonly Tab _tab;
public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Models.Document lcuSchemaDocument) public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation, ILogger<RequestViewModel> requestViewModelLogger, Models.Document document, Tab tab, System.Net.Http.HttpClient httpClient)
{ {
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked, lcuRequestViewModelLogger, lcuSchemaDocument); _activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked, requestViewModelLogger, document, tab, httpClient);
_onEndpointNavigation = onEndpointNavigation; _onEndpointNavigation = onEndpointNavigation;
_tab = tab;
_title = GetTitle(tab);
}
private string GetTitle(Tab tab)
{
return tab switch
{
Tab.LCU => "LCU",
Tab.GameClient => "Game Client",
_ => string.Empty,
};
} }
private void OnClicked(ObservableObject viewModel) private void OnClicked(ObservableObject viewModel)
@@ -28,7 +41,7 @@ public partial class EndpointsNavigationViewModel : ObservableObject
ActiveViewModel = viewModel; ActiveViewModel = viewModel;
if (viewModel is EndpointViewModel endpoint) if (viewModel is EndpointViewModel endpoint)
{ {
Title = endpoint.Title; Title = $"{GetTitle(_tab)} - {endpoint.Title}";
_onEndpointNavigation.Invoke(endpoint.Title, Guid); _onEndpointNavigation.Invoke(endpoint.Title, Guid);
} }
} }
@@ -37,7 +50,7 @@ public partial class EndpointsNavigationViewModel : ObservableObject
private void GoBack() private void GoBack()
{ {
ActiveViewModel = EndpointsViewModel; ActiveViewModel = EndpointsViewModel;
Title = string.Empty; Title = GetTitle(_tab);
_onEndpointNavigation.Invoke(null, Guid); _onEndpointNavigation.Invoke(null, Guid);
} }
} }

View File

@@ -2,15 +2,22 @@
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Needlework.Net.Models;
using Needlework.Net.ViewModels.Shared; using Needlework.Net.ViewModels.Shared;
using System; using System;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Needlework.Net.ViewModels.Pages.Endpoints; namespace Needlework.Net.ViewModels.Pages.Endpoints;
public enum Tab
{
LCU,
GameClient
}
public partial class EndpointsTabViewModel : PageBase public partial class EndpointsTabViewModel : PageBase
{ {
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>(); public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
@@ -18,32 +25,41 @@ public partial class EndpointsTabViewModel : PageBase
[ObservableProperty] private bool _isBusy = true; [ObservableProperty] private bool _isBusy = true;
private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger; private readonly ILogger<RequestViewModel> _requestViewModelLogger;
private readonly DataSource _dataSource; private readonly DataSource _dataSource;
private readonly HttpClient _httpClient;
public EndpointsTabViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, DataSource dataSource) : base("Endpoints", "list-alt", -500) public EndpointsTabViewModel(ILogger<RequestViewModel> requestViewModelLogger, DataSource dataSource, HttpClient httpClient) : base("Endpoints", "list-alt", -500)
{ {
_lcuRequestViewModelLogger = lcuRequestViewModelLogger; _requestViewModelLogger = requestViewModelLogger;
_dataSource = dataSource; _dataSource = dataSource;
WeakReferenceMessenger.Default.RegisterAll(this); _httpClient = httpClient;
} }
public override async Task InitializeAsync() public override async Task InitializeAsync()
{ {
var document = await _dataSource.GetLcuSchemaDocumentAsync(); await Dispatcher.UIThread.Invoke(async () => await AddEndpoint(Tab.LCU));
Plugins.Clear();
Plugins.AddRange(document.Plugins.Keys);
await Dispatcher.UIThread.Invoke(AddEndpoint);
IsBusy = false; IsBusy = false;
IsInitialized = true; IsInitialized = true;
} }
[RelayCommand] [RelayCommand]
private async Task AddEndpoint() private async Task AddEndpoint(Tab tab)
{ {
var lcuSchemaDocument = await _dataSource.GetLcuSchemaDocumentAsync(); Document document = tab switch
{
Tab.LCU => await _dataSource.GetLcuSchemaDocumentAsync(),
Tab.GameClient => await _dataSource.GetLolClientDocumentAsync(),
_ => throw new NotImplementedException(),
};
Plugins.Clear();
Plugins.AddRange(document.Plugins.Keys);
var vm = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation, _requestViewModelLogger, document, tab, _httpClient);
Endpoints.Add(new() Endpoints.Add(new()
{ {
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation, _lcuRequestViewModelLogger, lcuSchemaDocument), Content = vm,
Header = vm.Title,
Selected = true Selected = true
}); });
} }
@@ -54,7 +70,7 @@ public partial class EndpointsTabViewModel : PageBase
{ {
if (endpoint.Content.Guid.Equals(guid)) if (endpoint.Content.Guid.Equals(guid))
{ {
endpoint.Header = title ?? "Endpoints"; endpoint.Header = endpoint.Content.Title;
break; break;
} }
} }
@@ -63,7 +79,7 @@ public partial class EndpointsTabViewModel : PageBase
public partial class EndpointItem : ObservableObject public partial class EndpointItem : ObservableObject
{ {
[ObservableProperty] private string _header = "Endpoints"; [ObservableProperty] private string _header = string.Empty;
public IconSource IconSource { get; set; } = new SymbolIconSource() { Symbol = Symbol.Document, FontSize = 20.0, Foreground = Avalonia.Media.Brushes.White }; public IconSource IconSource { get; set; } = new SymbolIconSource() { Symbol = Symbol.Document, FontSize = 20.0, Foreground = Avalonia.Media.Brushes.White };
public bool Selected { get; set; } = false; public bool Selected { get; set; } = false;
public required EndpointsNavigationViewModel Content { get; init; } public required EndpointsNavigationViewModel Content { get; init; }

View File

@@ -6,6 +6,7 @@ using Needlework.Net.Models;
using Needlework.Net.ViewModels.Shared; using Needlework.Net.ViewModels.Shared;
using System; using System;
using System.Linq; using System.Linq;
using System.Net.Http;
namespace Needlework.Net.ViewModels.Pages.Endpoints; namespace Needlework.Net.ViewModels.Pages.Endpoints;
@@ -19,16 +20,20 @@ public partial class EndpointsViewModel : ObservableObject
public Action<ObservableObject> OnClicked { get; } public Action<ObservableObject> OnClicked { get; }
private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger; private readonly ILogger<RequestViewModel> _requestViewModelLogger;
private readonly Document _lcuSchemaDocument; private readonly Document _document;
private readonly Tab _tab;
private readonly HttpClient _httpClient;
public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Models.Document lcuSchemaDocument) public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked, ILogger<RequestViewModel> requestViewModelLogger, Models.Document document, Tab tab, System.Net.Http.HttpClient httpClient)
{ {
Plugins = new AvaloniaList<string>(plugins); Plugins = new AvaloniaList<string>(plugins);
Query = new AvaloniaList<string>(plugins); Query = new AvaloniaList<string>(plugins);
OnClicked = onClicked; OnClicked = onClicked;
_lcuRequestViewModelLogger = lcuRequestViewModelLogger; _requestViewModelLogger = requestViewModelLogger;
_lcuSchemaDocument = lcuSchemaDocument; _document = document;
_tab = tab;
_httpClient = httpClient;
} }
partial void OnSearchChanged(string value) partial void OnSearchChanged(string value)
@@ -45,6 +50,6 @@ public partial class EndpointsViewModel : ObservableObject
{ {
if (string.IsNullOrEmpty(value)) return; if (string.IsNullOrEmpty(value)) return;
OnClicked.Invoke(new EndpointViewModel(value, _lcuRequestViewModelLogger, _lcuSchemaDocument)); OnClicked.Invoke(new EndpointViewModel(value, _requestViewModelLogger, _document, _tab, _httpClient));
} }
} }

View File

@@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Needlework.Net.Models; using Needlework.Net.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@@ -21,23 +22,23 @@ public partial class OperationViewModel : ObservableObject
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; } public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
public string? RequestTemplate { get; } public string? RequestTemplate { get; }
public OperationViewModel(OpenApiOperation operation, Models.Document lcuSchemaDocument) public OperationViewModel(OpenApiOperation operation, Models.Document document)
{ {
Summary = operation.Summary ?? string.Empty; Summary = operation.Summary ?? string.Empty;
Description = operation.Description ?? string.Empty; Description = operation.Description ?? string.Empty;
IsRequestBody = operation.RequestBody != null; IsRequestBody = operation.RequestBody != null;
ReturnType = GetReturnType(operation.Responses); ReturnType = GetReturnType(operation.Responses);
RequestClasses = GetRequestClasses(operation.RequestBody, lcuSchemaDocument); RequestClasses = GetRequestClasses(operation.RequestBody, document);
ResponseClasses = GetResponseClasses(operation.Responses, lcuSchemaDocument); ResponseClasses = GetResponseClasses(operation.Responses, document);
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path); PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query); QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
RequestBodyType = GetRequestBodyType(operation.RequestBody); RequestBodyType = GetRequestBodyType(operation.RequestBody);
RequestTemplate = GetRequestTemplate(operation.RequestBody, lcuSchemaDocument); RequestTemplate = GetRequestTemplate(operation.RequestBody, document);
} }
private string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document lcuSchemaDocument) private string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document)
{ {
var requestClasses = GetRequestClasses(requestBody, lcuSchemaDocument); var requestClasses = GetRequestClasses(requestBody, document);
if (requestClasses.Count == 0) if (requestClasses.Count == 0)
{ {
var type = GetRequestBodyType(requestBody); var type = GetRequestBodyType(requestBody);
@@ -132,17 +133,40 @@ public partial class OperationViewModel : ObservableObject
return pathParameters; return pathParameters;
} }
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document lcuSchemaDocument) private bool TryGetResponse(OpenApiResponses responses, [NotNullWhen(true)] out OpenApiResponse? response)
{ {
if (responses.TryGetValue("2XX", out var response) response = null;
&& response.Content.TryGetValue("application/json", out var media)) var flag = false;
if (responses.TryGetValue("2XX", out var x))
{ {
var document = lcuSchemaDocument.OpenApiDocument; response = x;
flag = true;
}
else if (responses.TryGetValue("200", out var y))
{
response = y;
flag = true;
}
return flag;
}
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document document)
{
if (!TryGetResponse(responses, out var response))
return [];
if (response.Content.TryGetValue("application/json", out var media))
{
var rawDocument = document.OpenApiDocument;
var schema = media.Schema; var schema = media.Schema;
if (schema == null) return [];
AvaloniaList<PropertyClassViewModel> propertyClasses = []; AvaloniaList<PropertyClassViewModel> propertyClasses = [];
WalkSchema(schema, propertyClasses, document); WalkSchema(schema, propertyClasses, rawDocument);
return propertyClasses; return propertyClasses;
} }
return []; return [];
} }
@@ -185,12 +209,12 @@ public partial class OperationViewModel : ObservableObject
|| type.Contains("number")); || type.Contains("number"));
} }
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document lcuSchemaDocument) private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document document)
{ {
if (requestBody == null) return []; if (requestBody == null) return [];
if (requestBody.Content.TryGetValue("application/json", out var media)) if (requestBody.Content.TryGetValue("application/json", out var media))
{ {
var document = lcuSchemaDocument.OpenApiDocument; var rawDocument = document.OpenApiDocument;
var schema = media.Schema; var schema = media.Schema;
if (schema == null) return []; if (schema == null) return [];
@@ -198,9 +222,9 @@ public partial class OperationViewModel : ObservableObject
if (IsComponent(type)) if (IsComponent(type))
{ {
var componentId = GetComponentId(schema); var componentId = GetComponentId(schema);
var componentSchema = document.Components.Schemas[componentId]; var componentSchema = rawDocument.Components.Schemas[componentId];
AvaloniaList<PropertyClassViewModel> propertyClasses = []; AvaloniaList<PropertyClassViewModel> propertyClasses = [];
WalkSchema(componentSchema, propertyClasses, document); WalkSchema(componentSchema, propertyClasses, rawDocument);
return propertyClasses; return propertyClasses;
} }
} }
@@ -209,12 +233,15 @@ public partial class OperationViewModel : ObservableObject
private string GetReturnType(OpenApiResponses responses) private string GetReturnType(OpenApiResponses responses)
{ {
if (responses.TryGetValue("2XX", out var response) if (!TryGetResponse(responses, out var response))
&& response.Content.TryGetValue("application/json", out var media)) return "none";
if (response.Content.TryGetValue("application/json", out var media))
{ {
var schema = media.Schema; var schema = media.Schema;
return GetSchemaType(schema); return GetSchemaType(schema);
} }
return "none"; return "none";
} }
@@ -223,6 +250,7 @@ public partial class OperationViewModel : ObservableObject
if (schema.Reference != null) return schema.Reference.Id; if (schema.Reference != null) return schema.Reference.Id;
if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id; if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}"; if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}";
if (schema.Type == "array" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]"; if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]";
if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]"; if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]";
if (schema.Type == "array") return $"{schema.Items.Type}[]"; if (schema.Type == "array") return $"{schema.Items.Type}[]";

View File

@@ -17,13 +17,13 @@ public partial class PathOperationViewModel : ObservableObject
public string Url { get; } public string Url { get; }
[ObservableProperty] private bool _isBusy; [ObservableProperty] private bool _isBusy;
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest; [ObservableProperty] private Lazy<RequestViewModel> _request;
public PathOperationViewModel(PathOperation pathOperation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Document lcuSchemaDocument) public PathOperationViewModel(PathOperation pathOperation, ILogger<RequestViewModel> requestViewModelLogger, Document document, Tab tab, System.Net.Http.HttpClient httpClient)
{ {
Path = pathOperation.Path; Path = pathOperation.Path;
Operation = new OperationViewModel(pathOperation.Operation, lcuSchemaDocument); Operation = new OperationViewModel(pathOperation.Operation, document);
LcuRequest = new(() => new LcuRequestViewModel(lcuRequestViewModelLogger) Request = new(() => new RequestViewModel(requestViewModelLogger, tab, httpClient)
{ {
Method = pathOperation.Method.ToUpper() Method = pathOperation.Method.ToUpper()
}); });
@@ -50,8 +50,8 @@ public partial class PathOperationViewModel : ObservableObject
} }
} }
LcuRequest.Value.RequestPath = sb.ToString(); Request.Value.RequestPath = sb.ToString();
await LcuRequest.Value.ExecuteAsync(); await Request.Value.ExecuteAsync();
} }
[RelayCommand] [RelayCommand]

View File

@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Needlework.Net.Messages; using Needlework.Net.Messages;
using Needlework.Net.ViewModels.MainWindow; using Needlework.Net.ViewModels.MainWindow;
using Needlework.Net.ViewModels.Pages.Endpoints;
using System; using System;
using System.Net.Http; using System.Net.Http;
using System.Text.Json; using System.Text.Json;
@@ -12,7 +13,7 @@ using System.Threading.Tasks;
namespace Needlework.Net.ViewModels.Shared; namespace Needlework.Net.ViewModels.Shared;
public partial class LcuRequestViewModel : ObservableObject public partial class RequestViewModel : ObservableObject
{ {
[ObservableProperty] private string? _method = "GET"; [ObservableProperty] private string? _method = "GET";
[ObservableProperty] private SolidColorBrush _color = new(GetColor("GET")); [ObservableProperty] private SolidColorBrush _color = new(GetColor("GET"));
@@ -29,14 +30,18 @@ public partial class LcuRequestViewModel : ObservableObject
[ObservableProperty] private string? _responseAuthorization = null; [ObservableProperty] private string? _responseAuthorization = null;
[ObservableProperty] private string? _responseBody = null; [ObservableProperty] private string? _responseBody = null;
public event EventHandler<LcuRequestViewModel>? RequestText; public event EventHandler<RequestViewModel>? RequestText;
public event EventHandler<string>? UpdateText; public event EventHandler<string>? UpdateText;
private readonly ILogger<LcuRequestViewModel> _logger; private readonly ILogger<RequestViewModel> _logger;
private readonly Tab _tab;
private readonly HttpClient _httpClient;
public LcuRequestViewModel(ILogger<LcuRequestViewModel> logger) public RequestViewModel(ILogger<RequestViewModel> logger, Pages.Endpoints.Tab tab, HttpClient httpClient)
{ {
_logger = logger; _logger = logger;
_tab = tab;
_httpClient = httpClient;
} }
partial void OnMethodChanged(string? oldValue, string? newValue) partial void OnMethodChanged(string? oldValue, string? newValue)
@@ -47,25 +52,80 @@ public partial class LcuRequestViewModel : ObservableObject
} }
public async Task ExecuteAsync() public async Task ExecuteAsync()
{
switch (_tab)
{
case Tab.LCU:
await ExecuteLcuAsync();
break;
case Tab.GameClient:
await ExecuteGameClientAsync();
break;
default:
break;
}
}
private async Task ExecuteGameClientAsync()
{ {
try try
{ {
IsRequestBusy = true; IsRequestBusy = true;
if (string.IsNullOrEmpty(RequestPath)) if (string.IsNullOrEmpty(RequestPath))
throw new Exception("Path is empty."); throw new Exception("Path is empty.");
var method = GetMethod();
var method = Method switch _logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath));
RequestText?.Invoke(this, this);
var content = new StringContent(RequestBody ?? string.Empty, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
var responsePath = $"https://127.0.0.1:2999{RequestPath}";
var response = await _httpClient.SendAsync(new HttpRequestMessage(method, responsePath) { Content = content });
var responseBody = await response.Content.ReadAsByteArrayAsync();
var body = responseBody.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
if (body.Length > App.MaxCharacters)
{ {
"GET" => HttpMethod.Get, WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body));
"POST" => HttpMethod.Post, UpdateText?.Invoke(this, string.Empty);
"PUT" => HttpMethod.Put, }
"DELETE" => HttpMethod.Delete, else
"HEAD" => HttpMethod.Head, {
"PATCH" => HttpMethod.Patch, ResponseBody = body;
"OPTIONS" => HttpMethod.Options, UpdateText?.Invoke(this, body);
"TRACE" => HttpMethod.Trace, }
_ => throw new Exception("Method is not selected or missing."),
}; ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
ResponsePath = responsePath;
}
catch (Exception ex)
{
_logger.LogError(ex, "Request failed: {Tuple}", (Method, RequestPath));
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
UpdateText?.Invoke(this, string.Empty);
ResponseStatus = null;
ResponsePath = null;
ResponseAuthentication = null;
ResponseAuthorization = null;
ResponseUsername = null;
ResponsePassword = null;
ResponseBody = null;
}
finally
{
IsRequestBusy = false;
}
}
private async Task ExecuteLcuAsync()
{
try
{
IsRequestBusy = true;
if (string.IsNullOrEmpty(RequestPath))
throw new Exception("Path is empty.");
var method = GetMethod();
_logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath)); _logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath));
@@ -116,6 +176,22 @@ public partial class LcuRequestViewModel : ObservableObject
} }
} }
private HttpMethod GetMethod()
{
return Method switch
{
"GET" => HttpMethod.Get,
"POST" => HttpMethod.Post,
"PUT" => HttpMethod.Put,
"DELETE" => HttpMethod.Delete,
"HEAD" => HttpMethod.Head,
"PATCH" => HttpMethod.Patch,
"OPTIONS" => HttpMethod.Options,
"TRACE" => HttpMethod.Trace,
_ => throw new Exception("Method is not selected or missing."),
};
}
private static Color GetColor(string method) => method switch private static Color GetColor(string method) => method switch
{ {
"GET" => Avalonia.Media.Color.FromRgb(95, 99, 186), "GET" => Avalonia.Media.Color.FromRgb(95, 99, 186),

View File

@@ -19,13 +19,13 @@
<StackPanel Margin="0 0 0 16"> <StackPanel Margin="0 0 0 16">
<Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto"> <Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto">
<ComboBox ItemsSource="{Binding RequestMethods}" <ComboBox ItemsSource="{Binding RequestMethods}"
SelectedItem="{Binding LcuRequest.Method}" SelectedItem="{Binding Request.Method}"
Margin="0 0 8 0" Margin="0 0 8 0"
Grid.Row="0" Grid.Row="0"
Grid.Column="0"/> Grid.Column="0"/>
<AutoCompleteBox <AutoCompleteBox
ItemsSource="{Binding RequestPaths}" ItemsSource="{Binding RequestPaths}"
Text="{Binding LcuRequest.RequestPath}" Text="{Binding Request.RequestPath}"
MaxDropDownHeight="400" MaxDropDownHeight="400"
FilterMode="StartsWith" FilterMode="StartsWith"
Grid.Row="0" Grid.Row="0"
@@ -49,7 +49,7 @@
<TextBox IsReadOnly="True" <TextBox IsReadOnly="True"
Grid.Row="0" Grid.Row="0"
Grid.Column="0" Grid.Column="0"
Text="{Binding LcuRequest.ResponsePath}"/> Text="{Binding Request.ResponsePath}"/>
<avaloniaEdit:TextEditor <avaloniaEdit:TextEditor
Name="RequestEditor" Name="RequestEditor"
Text="" Text=""
@@ -69,7 +69,7 @@
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Grid.Row="0" Grid.Row="0"
Grid.Column="0"> Grid.Column="0">
<Button Content="{Binding LcuRequest.ResponseStatus}" <Button Content="{Binding Request.ResponseStatus}"
FontSize="12" FontSize="12"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</StackPanel> </StackPanel>

View File

@@ -28,13 +28,13 @@ public partial class ConsoleView : UserControl
_requestEditor?.ApplyJsonEditorSettings(); _requestEditor?.ApplyJsonEditorSettings();
var vm = (ConsoleViewModel)DataContext!; var vm = (ConsoleViewModel)DataContext!;
vm.LcuRequest.RequestText += LcuRequest_RequestText; ; vm.Request.RequestText += LcuRequest_RequestText; ;
vm.LcuRequest.UpdateText += LcuRequest_UpdateText; vm.Request.UpdateText += LcuRequest_UpdateText;
OnBaseThemeChanged(Application.Current!.ActualThemeVariant); OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
} }
private void LcuRequest_RequestText(object? sender, ViewModels.Shared.LcuRequestViewModel e) private void LcuRequest_RequestText(object? sender, ViewModels.Shared.RequestViewModel e)
{ {
e.RequestBody = _requestEditor!.Text; e.RequestBody = _requestEditor!.Text;
} }
@@ -49,8 +49,8 @@ public partial class ConsoleView : UserControl
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);
var vm = (ConsoleViewModel)DataContext!; var vm = (ConsoleViewModel)DataContext!;
vm.LcuRequest.RequestText -= LcuRequest_RequestText; vm.Request.RequestText -= LcuRequest_RequestText;
vm.LcuRequest.UpdateText -= LcuRequest_UpdateText; vm.Request.UpdateText -= LcuRequest_UpdateText;
} }
private void OnBaseThemeChanged(ThemeVariant currentTheme) private void OnBaseThemeChanged(ThemeVariant currentTheme)

View File

@@ -64,8 +64,8 @@
VerticalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" TextAlignment="Center"
Margin="0 0 8 0" Margin="0 0 8 0"
Text="{Binding LcuRequest.Value.Method}" Text="{Binding Request.Value.Method}"
Background="{Binding LcuRequest.Value.Color}" Background="{Binding Request.Value.Color}"
FontSize="8" FontSize="8"
Width="50" Width="50"
Padding="10 2 10 2" Padding="10 2 10 2"
@@ -93,14 +93,14 @@
ColumnDefinitions="auto,*,auto"> ColumnDefinitions="auto,*,auto">
<TextBox Grid.Row="0" <TextBox Grid.Row="0"
Grid.Column="0" Grid.Column="0"
Text="{Binding SelectedPathOperation.LcuRequest.Value.Method}" Text="{Binding SelectedPathOperation.Request.Value.Method}"
FontSize="12" FontSize="12"
IsReadOnly="True" IsReadOnly="True"
Margin="0 0 8 0"/> Margin="0 0 8 0"/>
<TextBox Grid.Row="0" <TextBox Grid.Row="0"
Grid.Column="1" Grid.Column="1"
FontSize="12" FontSize="12"
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponsePath}" Text="{Binding SelectedPathOperation.Request.Value.ResponsePath}"
IsReadOnly="True"/> IsReadOnly="True"/>
<StackPanel Grid.Row="0" <StackPanel Grid.Row="0"
Grid.Column="2" Grid.Column="2"
@@ -194,7 +194,7 @@
Grid.Column="1" Grid.Column="1"
Margin="0 0 0 8" Margin="0 0 0 8"
IsReadOnly="True" IsReadOnly="True"
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponseUsername}" /> Text="{Binding SelectedPathOperation.Request.Value.ResponseUsername}" />
<TextBlock FontSize="12" <TextBlock FontSize="12"
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
@@ -206,7 +206,7 @@
Grid.Column="1" Grid.Column="1"
Margin="0 0 0 8" Margin="0 0 0 8"
IsReadOnly="True" IsReadOnly="True"
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponsePassword}"/> Text="{Binding SelectedPathOperation.Request.Value.ResponsePassword}"/>
<TextBlock FontSize="12" <TextBlock FontSize="12"
Grid.Row="2" Grid.Row="2"
Grid.Column="0" Grid.Column="0"
@@ -217,7 +217,7 @@
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
IsReadOnly="True" IsReadOnly="True"
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponseAuthorization}"/> Text="{Binding SelectedPathOperation.Request.Value.ResponseAuthorization}"/>
</Grid> </Grid>
</TabItem> </TabItem>
<TabItem Header="Schemas"> <TabItem Header="Schemas">
@@ -309,7 +309,7 @@
FontSize="10" FontSize="10"
Padding="12 4 12 4" Padding="12 4 12 4"
Classes="Flat" Classes="Flat"
Content="{Binding SelectedPathOperation.LcuRequest.Value.ResponseStatus}"/> Content="{Binding SelectedPathOperation.Request.Value.ResponseStatus}"/>
</StackPanel> </StackPanel>
<Grid Grid.Row="1" Grid.Column="4"> <Grid Grid.Row="1" Grid.Column="4">

View File

@@ -13,7 +13,7 @@ public partial class EndpointView : UserControl
{ {
private TextEditor? _requestEditor; private TextEditor? _requestEditor;
private TextEditor? _responseEditor; private TextEditor? _responseEditor;
private LcuRequestViewModel? _lcuRequestVm; private RequestViewModel? _lcuRequestVm;
public EndpointView() public EndpointView()
{ {
@@ -34,9 +34,9 @@ public partial class EndpointView : UserControl
if (vm.SelectedPathOperation != null) if (vm.SelectedPathOperation != null)
{ {
_lcuRequestVm = vm.SelectedPathOperation.LcuRequest.Value; _lcuRequestVm = vm.SelectedPathOperation.Request.Value;
vm.SelectedPathOperation.LcuRequest.Value.RequestText += LcuRequest_RequestText; vm.SelectedPathOperation.Request.Value.RequestText += LcuRequest_RequestText;
vm.SelectedPathOperation.LcuRequest.Value.UpdateText += LcuRequest_UpdateText; vm.SelectedPathOperation.Request.Value.UpdateText += LcuRequest_UpdateText;
} }
OnBaseThemeChanged(Application.Current!.ActualThemeVariant); OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
@@ -53,10 +53,10 @@ public partial class EndpointView : UserControl
_lcuRequestVm.RequestText -= LcuRequest_RequestText; _lcuRequestVm.RequestText -= LcuRequest_RequestText;
_lcuRequestVm.UpdateText -= LcuRequest_UpdateText; _lcuRequestVm.UpdateText -= LcuRequest_UpdateText;
} }
vm.SelectedPathOperation.LcuRequest.Value.RequestText += LcuRequest_RequestText; vm.SelectedPathOperation.Request.Value.RequestText += LcuRequest_RequestText;
vm.SelectedPathOperation.LcuRequest.Value.UpdateText += LcuRequest_UpdateText; vm.SelectedPathOperation.Request.Value.UpdateText += LcuRequest_UpdateText;
_lcuRequestVm = vm.SelectedPathOperation.LcuRequest.Value; _lcuRequestVm = vm.SelectedPathOperation.Request.Value;
_responseEditor!.Text = vm.SelectedPathOperation.LcuRequest.Value.ResponseBody ?? string.Empty; _responseEditor!.Text = vm.SelectedPathOperation.Request.Value.ResponseBody ?? string.Empty;
} }
} }
@@ -81,7 +81,7 @@ public partial class EndpointView : UserControl
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus); currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
} }
private void LcuRequest_RequestText(object? sender, LcuRequestViewModel e) private void LcuRequest_RequestText(object? sender, RequestViewModel e)
{ {
e.RequestBody = _requestEditor!.Text; e.RequestBody = _requestEditor!.Text;
} }

View File

@@ -3,6 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="https://github.com/projektanker/icons.avalonia" xmlns:i="https://github.com/projektanker/icons.avalonia"
Name="EndpointsTab"
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints" xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:controls="using:Needlework.Net.Controls" xmlns:controls="using:Needlework.Net.Controls"
@@ -14,6 +15,7 @@
<Grid> <Grid>
<ui:TabView TabItems="{Binding Endpoints}" <ui:TabView TabItems="{Binding Endpoints}"
AddTabButtonCommand="{Binding AddEndpointCommand}" AddTabButtonCommand="{Binding AddEndpointCommand}"
AddTabButtonCommandParameter="{x:Static vm:Tab.LCU}"
TabCloseRequested="TabView_TabCloseRequested"> TabCloseRequested="TabView_TabCloseRequested">
<!--Need to override Tab header for Mica theme...--> <!--Need to override Tab header for Mica theme...-->
<ui:TabView.Resources> <ui:TabView.Resources>
@@ -44,7 +46,24 @@
Content="{Binding}"> Content="{Binding}">
<ui:TabViewItem.ContentTemplate> <ui:TabViewItem.ContentTemplate>
<DataTemplate DataType="vm:EndpointItem"> <DataTemplate DataType="vm:EndpointItem">
<ContentControl Content="{Binding Content}"/> <Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
<Menu Grid.Row="0"
Grid.Column="0">
<MenuItem Header="_New tab">
<MenuItem Header="LCU"
Command="{Binding #EndpointsTab.((vm:EndpointsTabViewModel)DataContext).AddEndpointCommand}"
CommandParameter="{x:Static vm:Tab.LCU}"/>
<MenuItem Header="Game Client"
Command="{Binding #EndpointsTab.((vm:EndpointsTabViewModel)DataContext).AddEndpointCommand}"
CommandParameter="{x:Static vm:Tab.GameClient}"/>
</MenuItem>
</Menu>
<Separator Grid.Row="1"
Grid.Column="0"/>
<ContentControl Grid.Row="2"
Grid.Column="0"
Content="{Binding Content}"/>
</Grid>
</DataTemplate> </DataTemplate>
</ui:TabViewItem.ContentTemplate> </ui:TabViewItem.ContentTemplate>
</ui:TabViewItem> </ui:TabViewItem>

View File

@@ -40,7 +40,7 @@
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"> <TextBlock Theme="{StaticResource TitleTextBlockStyle}">
Welcome to Needlework.Net Welcome to Needlework.Net
</TextBlock> </TextBlock>
<TextBlock>Get started with LCU development by clicking on the endpoints tab in the left panel.</TextBlock> <TextBlock>Get started with LCU or Game Client development by clicking on the endpoints tab in the left panel.</TextBlock>
</StackPanel> </StackPanel>
</Border> </Border>
<controls:Card Margin="12"> <controls:Card Margin="12">