This commit is contained in:
BlossomiShymae
2024-08-08 21:16:02 -05:00
parent a8741cd352
commit ed89a1d543
61 changed files with 1745 additions and 338 deletions

View File

@@ -2,17 +2,25 @@
using BlossomiShymae.GrrrLCU;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Needlework.Net.Desktop.Messages;
using Needlework.Net.Desktop.Services;
using Needlework.Net.Desktop.Views;
using SukiUI.Controls;
using System;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class ConsoleViewModel : PageBase
public partial class ConsoleViewModel : PageBase, IRecipient<OopsiesWindowRequestedMessage>, IRecipient<DataReadyMessage>
{
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
[ObservableProperty] private bool _isBusy = true;
[ObservableProperty] private bool _isRequestBusy = false;
[ObservableProperty] private IAvaloniaReadOnlyList<string> _requestPaths = new AvaloniaList<string>();
[ObservableProperty] private string? _requestMethodSelected = "GET";
[ObservableProperty] private string? _requestPath = null;
[ObservableProperty] private string? _requestBody = null;
@@ -20,10 +28,14 @@ namespace Needlework.Net.Desktop.ViewModels
[ObservableProperty] private string? _responseStatus = null;
[ObservableProperty] private string? _responseAuthentication = null;
public event EventHandler<TextUpdatedEventArgs>? ResponseBodyUpdated;
public WindowService WindowService { get; }
public ConsoleViewModel() : base("Console", Material.Icons.MaterialIconKind.Console, -100)
public ConsoleViewModel(WindowService windowService) : base("Console", Material.Icons.MaterialIconKind.Console, -200)
{
WindowService = windowService;
WeakReferenceMessenger.Default.Register<OopsiesWindowRequestedMessage, string>(this, nameof(ConsoleView));
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
}
[RelayCommand]
@@ -31,6 +43,7 @@ namespace Needlework.Net.Desktop.ViewModels
{
try
{
IsRequestBusy = true;
if (string.IsNullOrEmpty(RequestPath)) throw new Exception("Path is empty.");
var method = RequestMethodSelected switch
@@ -47,29 +60,43 @@ namespace Needlework.Net.Desktop.ViewModels
};
var processInfo = Connector.GetProcessInfo();
var response = await Connector.SendAsync(method, RequestPath) ?? throw new Exception("Response is null.");
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "ConsoleRequestEditor").Response;
var content = new StringContent(Regex.Replace(requestBody, @"\s+", ""), new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
var response = await Connector.SendAsync(method, RequestPath, content) ?? throw new Exception("Response is null.");
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
var body = await response.Content.ReadAsStringAsync();
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
ResponseStatus = response.StatusCode.ToString();
ResponsePath = $"https://127.0.0.1/{processInfo.AppPort}{RequestPath}";
ResponseAuthentication = riotAuthentication.Value;
ResponseBodyUpdated?.Invoke(this, new(body));
});
ResponseStatus = response.StatusCode.ToString();
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
ResponseAuthentication = $"Basic {riotAuthentication.Value}";
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(body), nameof(ConsoleViewModel));
}
catch (Exception ex)
{
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
ResponseStatus = null;
ResponsePath = null;
ResponseAuthentication = null;
ResponseBodyUpdated?.Invoke(this, new(string.Empty));
});
ResponseStatus = null;
ResponsePath = null;
ResponseAuthentication = null;
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
}
finally
{
IsRequestBusy = false;
}
}
public void Receive(OopsiesWindowRequestedMessage message)
{
WindowService.ShowOopsiesWindow(message.Value);
}
public void Receive(DataReadyMessage message)
{
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
{
RequestPaths = new AvaloniaList<string>([.. message.Value.Paths]);
IsBusy = false;
});
}
}
}

View File

@@ -1,10 +1,26 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Needlework.Net.Desktop.Messages;
using SukiUI.Controls;
using System.Linq;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class EndpointViewModel(string endpoint) : ObservableObject
public partial class EndpointViewModel : ObservableObject, ISukiStackPageTitleProvider
{
public string Endpoint { get; } = endpoint;
public string Title => $"Needlework.Net - {Endpoint}";
public string Endpoint { get; }
public string Title => Endpoint;
[ObservableProperty] private IAvaloniaReadOnlyList<PathOperationViewModel> _pathOperations;
[ObservableProperty] private PathOperationViewModel? _selectedPathOperation;
public EndpointViewModel(string endpoint)
{
Endpoint = endpoint;
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x)));
}
}
}

View File

@@ -0,0 +1,21 @@
using CommunityToolkit.Mvvm.ComponentModel;
using SukiUI.Controls;
using System.Net.Http;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class EndpointsContainerViewModel : PageBase
{
[ObservableProperty] private ISukiStackPageTitleProvider _activeViewModel;
public EndpointsContainerViewModel(HttpClient httpClient) : base("Endpoints", Material.Icons.MaterialIconKind.Hub, -500)
{
_activeViewModel = new EndpointsViewModel(httpClient, OnClicked);
}
private void OnClicked(ISukiStackPageTitleProvider viewModel)
{
ActiveViewModel = viewModel;
}
}
}

View File

@@ -1,57 +1,55 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Needlework.Net.Core;
using Needlework.Net.Desktop.Services;
using System.Collections.Generic;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Needlework.Net.Desktop.Messages;
using SukiUI.Controls;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class EndpointsViewModel : PageBase
public partial class EndpointsViewModel : ObservableObject, IRecipient<DataReadyMessage>, ISukiStackPageTitleProvider
{
public HttpClient HttpClient { get; }
public DialogService DialogService { get; }
public string Title => "Endpoints";
public Action<ISukiStackPageTitleProvider> OnClicked;
[ObservableProperty] private List<string> _plugins = [];
[ObservableProperty] private IAvaloniaReadOnlyList<string> _plugins = new AvaloniaList<string>();
[ObservableProperty] private bool _isBusy = true;
[ObservableProperty] private string _search = string.Empty;
[ObservableProperty] private List<string> _query = [];
[ObservableProperty] private IAvaloniaReadOnlyList<string> _query = new AvaloniaList<string>();
[ObservableProperty] private string? _selectedQuery = string.Empty;
public EndpointsViewModel(HttpClient httpClient, DialogService dialogService) : base("Endpoints", Material.Icons.MaterialIconKind.Hub, -500)
public EndpointsViewModel(HttpClient httpClient, Action<ISukiStackPageTitleProvider> onClicked)
{
HttpClient = httpClient;
DialogService = dialogService;
OnClicked = onClicked;
Task.Run(InitializeAsync);
WeakReferenceMessenger.Default.Register(this);
}
private async Task InitializeAsync()
public void Receive(DataReadyMessage message)
{
var handler = new LcuSchemaHandler(await Resources.GetOpenApiDocumentAsync(HttpClient));
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
Plugins = [.. handler.Plugins.Keys];
Query = [.. Plugins];
IsBusy = false;
});
IsBusy = false;
Plugins = new AvaloniaList<string>([.. message.Value.Plugins.Keys]);
Query = new AvaloniaList<string>([.. Plugins]);
}
partial void OnSearchChanged(string value)
{
if (!string.IsNullOrEmpty(Search))
Query = Plugins.Where(x => x.Contains(value)).ToList();
Query = new AvaloniaList<string>(Plugins.Where(x => x.Contains(value)));
else
Query = Plugins;
}
partial void OnSelectedQueryChanged(string? value)
{
if (string.IsNullOrEmpty(value))
return;
DialogService.ShowEndpoint(value);
if (string.IsNullOrEmpty(value)) return;
OnClicked.Invoke(new EndpointViewModel(value));
}
}
}

View File

@@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Threading;
namespace Needlework.Net.Desktop.ViewModels
{
@@ -16,27 +16,40 @@ namespace Needlework.Net.Desktop.ViewModels
public HomeViewModel() : base("Home", Material.Icons.MaterialIconKind.Home, int.MinValue)
{
Task.Run(async () => { while (true) { SetStatus(); await Task.Delay(TimeSpan.FromSeconds(5)); } });
StartProcessing();
}
private void SetStatus()
private void StartProcessing()
{
void Set(string text, Color color, string address)
var thread = new Thread(() =>
{
StatusText = text;
StatusForeground = new SolidColorBrush(color.ToUInt32());
StatusAddress = address;
}
while (true)
{
void Set(string text, Color color, string address)
{
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
{
StatusText = text;
StatusForeground = new SolidColorBrush(color.ToUInt32());
StatusAddress = address;
});
}
try
{
var processInfo = Connector.GetProcessInfo();
Avalonia.Threading.Dispatcher.UIThread.Post(() => Set("Online", Colors.Green, $"https://127.0.0.1:{processInfo.AppPort}/"));
}
catch (InvalidOperationException)
{
Avalonia.Threading.Dispatcher.UIThread.Post(() => Set("Offline", Colors.Red, "N/A"));
}
try
{
var processInfo = Connector.GetProcessInfo();
Set("Online", Colors.Green, $"https://127.0.0.1:{processInfo.AppPort}/");
}
catch (InvalidOperationException)
{
Set("Offline", Colors.Red, "N/A");
}
Thread.Sleep(TimeSpan.FromSeconds(5));
}
})
{ IsBackground = true };
thread.Start();
}
[RelayCommand]

View File

@@ -1,22 +1,60 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.OpenApi.Models;
using Needlework.Net.Core;
using Needlework.Net.Desktop.Messages;
using SukiUI.Controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class MainWindowViewModel : ObservableObject
public partial class MainWindowViewModel : ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>
{
public IAvaloniaReadOnlyList<PageBase> Pages { get; }
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
public MainWindowViewModel(IEnumerable<PageBase> pages)
public HttpClient HttpClient { get; }
public LcuSchemaHandler? LcuSchemaHandler { get; set; }
public OpenApiDocument? HostDocument { get; set; }
[ObservableProperty] private bool _isBusy = true;
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient)
{
Pages = new AvaloniaList<PageBase>(pages.OrderBy(x => x.Index).ThenBy(x => x.DisplayName));
HttpClient = httpClient;
WeakReferenceMessenger.Default.RegisterAll(this);
Task.Run(FetchDataAsync);
}
private async Task FetchDataAsync()
{
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
HostDocument = document;
var handler = new LcuSchemaHandler(document);
LcuSchemaHandler = handler;
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () => await SukiHost.ShowToast("OpenAPI Data Processed", "Some pages can now be used.", SukiUI.Enums.NotificationType.Success, TimeSpan.FromSeconds(5)));
IsBusy = false;
}
public void Receive(DataRequestMessage message)
{
message.Reply(LcuSchemaHandler!);
}
public void Receive(HostDocumentRequestMessage message)
{
message.Reply(HostDocument!);
}
[RelayCommand]

View File

@@ -0,0 +1,29 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Needlework.Net.Desktop.Messages;
using System.Diagnostics;
using System.IO;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class OopsiesWindowViewModel(string text) : ObservableObject
{
public string Text { get; } = text;
[RelayCommand]
private void OpenDefaultEditor()
{
var temp = Path.GetTempFileName().Replace(".tmp", ".json");
File.WriteAllText(temp, Text);
Process.Start("explorer", "\"" + temp + "\"");
CloseDialog();
}
[RelayCommand]
private void CloseDialog()
{
WeakReferenceMessenger.Default.Send(new OopsiesWindowCanceledMessage(null));
}
}
}

View File

@@ -0,0 +1,153 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.OpenApi.Models;
using Needlework.Net.Desktop.Messages;
using System.Collections.Generic;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class OperationViewModel : ObservableObject
{
public string Summary { get; }
public string Description { get; }
public string ReturnType { get; }
public bool IsRequestBody { get; }
public string? RequestBodyType { get; }
public IAvaloniaReadOnlyList<PropertyClassViewModel> RequestClasses { get; }
public IAvaloniaReadOnlyList<PropertyClassViewModel> ResponseClasses { get; }
public IAvaloniaReadOnlyList<ParameterViewModel> PathParameters { get; }
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
public OperationViewModel(OpenApiOperation operation)
{
Summary = operation.Summary ?? string.Empty;
Description = operation.Description ?? string.Empty;
IsRequestBody = operation.RequestBody != null;
ReturnType = GetReturnType(operation.Responses);
RequestClasses = GetRequestClasses(operation.RequestBody);
ResponseClasses = GetResponseClasses(operation.Responses);
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
RequestBodyType = GetRequestBodyType(operation.RequestBody);
}
private string? GetRequestBodyType(OpenApiRequestBody? requestBody)
{
if (requestBody == null) return null;
if (requestBody.Content.TryGetValue("application/json", out var media))
{
var schema = media.Schema;
return GetSchemaType(schema);
}
return null;
}
private AvaloniaList<ParameterViewModel> GetParameters(IList<OpenApiParameter> parameters, ParameterLocation location)
{
var pathParameters = new AvaloniaList<ParameterViewModel>();
foreach (var parameter in parameters)
{
if (parameter.In != location) break;
pathParameters.Add(new ParameterViewModel(parameter.Name, parameter.Schema.Type, parameter.Required));
}
return pathParameters;
}
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses)
{
if (responses.TryGetValue("2XX", out var response)
&& response.Content.TryGetValue("application/json", out var media))
{
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
var schema = media.Schema;
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
WalkSchema(schema, propertyClasses, document);
return propertyClasses;
}
return [];
}
private void WalkSchema(OpenApiSchema schema, AvaloniaList<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
{
var type = GetSchemaType(schema);
if (IsComponent(type))
{
string componentId = GetComponentId(schema);
var componentSchema = document.Components.Schemas[componentId];
var responseClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
propertyClasses.Add(responseClass);
foreach ((var _, var property) in componentSchema.Properties)
// Check for self-references like "LolLootLootOddsResponse"
// I blame dubble
if (IsComponent(GetSchemaType(property)) && componentId != GetComponentId(property))
WalkSchema(property, propertyClasses, document);
}
}
private static string GetComponentId(OpenApiSchema schema)
{
string componentId;
if (schema.Reference != null) componentId = schema.Reference.Id;
else if (schema.Items != null) componentId = schema.Items.Reference.Id;
else componentId = schema.AdditionalProperties.Reference.Id;
return componentId;
}
private static bool IsComponent(string type)
{
return !(type.Contains("object")
|| type.Contains("array")
|| type.Contains("bool")
|| type.Contains("string")
|| type.Contains("integer")
|| type.Contains("number"));
}
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody)
{
if (requestBody == null) return [];
if (requestBody.Content.TryGetValue("application/json", out var media))
{
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
var schema = media.Schema;
if (schema == null) return [];
var type = GetSchemaType(media.Schema);
if (IsComponent(type))
{
var componentId = GetComponentId(schema);
var componentSchema = document.Components.Schemas[componentId];
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
WalkSchema(componentSchema, propertyClasses, document);
return propertyClasses;
}
}
return [];
}
private string GetReturnType(OpenApiResponses responses)
{
if (responses.TryGetValue("2XX", out var response)
&& response.Content.TryGetValue("application/json", out var media))
{
var schema = media.Schema;
return GetSchemaType(schema);
}
return "none";
}
public static string GetSchemaType(OpenApiSchema schema)
{
if (schema.Reference != null) return schema.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 == "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") return $"{schema.Items.Type}[]";
return schema.Type;
}
}
}

View File

@@ -0,0 +1,20 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class ParameterViewModel : ObservableObject
{
public string Name { get; }
public string Type { get; }
public bool IsRequired { get; }
[ObservableProperty] private string? _value = null;
public ParameterViewModel(string name, string type, bool isRequired, string? value = null)
{
Name = name;
Type = type;
IsRequired = isRequired;
Value = value;
}
}
}

View File

@@ -0,0 +1,134 @@
using Avalonia.Media;
using BlossomiShymae.GrrrLCU;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Needlework.Net.Core;
using Needlework.Net.Desktop.Messages;
using SukiUI.Controls;
using System;
using System.Net.Http;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class PathOperationViewModel : ObservableObject
{
public string Method { get; }
public SolidColorBrush Color { get; }
public string Path { get; }
public OperationViewModel Operation { get; }
public ProcessInfo? ProcessInfo { get; }
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private string? _responsePath;
[ObservableProperty] private string? _responseStatus;
[ObservableProperty] private string? _responseAuthentication;
[ObservableProperty] private string? _responseUsername;
[ObservableProperty] private string? _responsePassword;
[ObservableProperty] private string? _responseAuthorization;
public PathOperationViewModel(PathOperation pathOperation)
{
Method = pathOperation.Method.ToUpper();
Color = new SolidColorBrush(GetColor(pathOperation.Method.ToUpper()));
Path = pathOperation.Path;
Operation = new OperationViewModel(pathOperation.Operation);
ProcessInfo = GetProcessInfo();
ResponsePath = ProcessInfo != null ? $"https://127.0.0.1:{ProcessInfo.AppPort}{Path}" : null;
ResponseUsername = ProcessInfo != null ? new RiotAuthentication(ProcessInfo.RemotingAuthToken).Username : null;
ResponsePassword = ProcessInfo != null ? new RiotAuthentication(ProcessInfo.RemotingAuthToken).Password : null;
ResponseAuthorization = ProcessInfo != null ? $"Basic {new RiotAuthentication(ProcessInfo.RemotingAuthToken).Value}" : null;
}
private ProcessInfo? GetProcessInfo()
{
try
{
var processInfo = Connector.GetProcessInfo();
return processInfo;
}
catch (Exception ex)
{
Task.Run(async () => await SukiHost.ShowToast("Error", ex.Message, SukiUI.Enums.NotificationType.Error));
}
return null;
}
[RelayCommand]
public async Task SendRequest()
{
try
{
IsBusy = true;
var method = Method.ToUpper() 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 missing.")
};
var processInfo = Connector.GetProcessInfo();
var path = Path;
foreach (var pathParameter in Operation.PathParameters)
{
path = path.Replace($"{{{pathParameter.Name}}}", pathParameter.Value);
}
var query = "";
foreach (var queryParameter in Operation.QueryParameters)
{
if (query.Length != 0 && !string.IsNullOrWhiteSpace(queryParameter.Value))
query += $"&{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}";
else if (query.Length == 0 && !string.IsNullOrWhiteSpace(queryParameter.Value))
query += $"?{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}";
}
var uri = $"{path}{query}";
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "EndpointRequestEditor").Response;
var content = new StringContent(Regex.Replace(requestBody, @"\s+", ""), new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
var response = await Connector.SendAsync(method, $"{uri}", content) ?? throw new Exception("Response is null.");
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
var responseBody = await response.Content.ReadAsStringAsync();
responseBody = !string.IsNullOrEmpty(responseBody) ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode}";
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{uri}";
ResponseAuthentication = $"Basic {riotAuthentication.Value}";
ResponseUsername = riotAuthentication.Username;
ResponsePassword = riotAuthentication.Password;
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(responseBody, "EndpointResponseEditor")));
}
catch (Exception ex)
{
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(string.Empty, "EndpointResponseEditor")));
}
finally
{
IsBusy = false;
}
}
public static Color GetColor(string method) => method switch
{
"GET" => Avalonia.Media.Color.FromRgb(95, 99, 186),
"POST" => Avalonia.Media.Color.FromRgb(103, 186, 95),
"PUT" => Avalonia.Media.Color.FromRgb(186, 139, 95),
"DELETE" => Avalonia.Media.Color.FromRgb(186, 95, 95),
"HEAD" => Avalonia.Media.Color.FromRgb(136, 95, 186),
"PATCH" => Avalonia.Media.Color.FromRgb(95, 186, 139),
_ => throw new InvalidOperationException("Method does not have assigned color.")
};
}
}

View File

@@ -1,7 +0,0 @@
namespace Needlework.Net.Desktop.ViewModels
{
public class PluginViewModel
{
public PluginViewModel() { }
}
}

View File

@@ -0,0 +1,36 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using System.Collections.Generic;
using System.Linq;
namespace Needlework.Net.Desktop.ViewModels
{
public class PropertyClassViewModel : ObservableObject
{
public string Id { get; }
public IAvaloniaReadOnlyList<PropertyFieldViewModel> PropertyFields { get; } = new AvaloniaList<PropertyFieldViewModel>();
public IAvaloniaReadOnlyList<PropertyEnumViewModel> PropertyEnums { get; } = new AvaloniaList<PropertyEnumViewModel>();
public PropertyClassViewModel(string id, IDictionary<string, OpenApiSchema> properties, IList<IOpenApiAny> enumValue)
{
AvaloniaList<PropertyFieldViewModel> propertyFields = [];
AvaloniaList<PropertyEnumViewModel> propertyEnums = [];
foreach ((var propertyName, var propertySchema) in properties)
{
var type = OperationViewModel.GetSchemaType(propertySchema);
var field = new PropertyFieldViewModel(propertyName, type);
propertyFields.Add(field);
}
if (enumValue != null && enumValue.Any())
{
var propertyEnum = new PropertyEnumViewModel(enumValue);
propertyEnums.Add(propertyEnum);
}
PropertyFields = propertyFields;
PropertyEnums = propertyEnums;
Id = id;
}
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.OpenApi.Any;
using System.Collections.Generic;
using System.Linq;
namespace Needlework.Net.Desktop.ViewModels
{
public class PropertyEnumViewModel
{
public string Type { get; } = "Enum";
public string Values { get; }
public PropertyEnumViewModel(IList<IOpenApiAny> enumValue)
{
Values = $"[{string.Join(", ", enumValue.Select(x => ((OpenApiString)x).Value).ToList())}]";
}
}
}

View File

@@ -0,0 +1,14 @@
namespace Needlework.Net.Desktop.ViewModels
{
public class PropertyFieldViewModel
{
public string Name { get; }
public string Type { get; }
public PropertyFieldViewModel(string name, string type)
{
Name = name;
Type = type;
}
}
}

View File

@@ -0,0 +1,103 @@
using BlossomiShymae.GrrrLCU;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Material.Icons;
using Needlework.Net.Desktop.Messages;
using Needlework.Net.Desktop.Services;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using Websocket.Client;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class WebsocketViewModel : PageBase
{
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
[ObservableProperty] private ObservableCollection<string> _eventLog = [];
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
[ObservableProperty] private string _search = string.Empty;
[ObservableProperty] private bool _isAttach = true;
[ObservableProperty] private bool _isTail = false;
[ObservableProperty] private string? _selectedEventLog = null;
private Dictionary<string, EventMessage> _events = [];
public WindowService WindowService { get; }
public List<string> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? [.. EventLog] : [.. EventLog.Where(x => x.ToLower().Contains(Search.ToLower()))];
public WebsocketViewModel(WindowService windowService) : base("Event Viewer", MaterialIconKind.Connection, -100)
{
WindowService = windowService;
var client = Connector.CreateLcuWebsocketClient();
client.EventReceived.Subscribe(OnMessage);
client.DisconnectionHappened.Subscribe(OnDisconnection);
client.ReconnectionHappened.Subscribe(OnReconnection);
client.Start();
client.Send(new EventMessage(RequestType.Subscribe, EventMessage.Kinds.OnJsonApiEvent));
}
[RelayCommand]
private void Clear()
{
EventLog = [];
}
partial void OnSelectedEventLogChanged(string? value)
{
if (value == null) return;
if (_events.TryGetValue(value, out var message))
{
var text = JsonSerializer.Serialize(message, App.JsonSerializerOptions);
if (text.Length >= App.MaxCharacters) WindowService.ShowOopsiesWindow(text);
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(text), nameof(WebsocketViewModel));
}
}
private void OnReconnection(ReconnectionInfo info)
{
Trace.WriteLine($"-- Reconnection --\n{JsonSerializer.Serialize(info, App.JsonSerializerOptions)}");
}
private void OnDisconnection(DisconnectionInfo info)
{
Trace.WriteLine($"-- Disconnection --\n{JsonSerializer.Serialize(info, App.JsonSerializerOptions)}");
}
private void OnMessage(EventMessage message)
{
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
{
if (!IsAttach) return;
var line = $"{DateTime.Now:HH:mm:ss.fff} {message.Data?.EventType.ToUpper()} {message.Data?.Uri}";
var log = EventLog.ToList();
Trace.WriteLine($"Message: {line}");
if (log.Count < 1000)
{
log.Add(line);
_events[line] = message;
}
else
{
var key = $"{log[0]}";
log.RemoveAt(0);
_events.Remove(key);
log.Add(line);
_events[line] = message;
}
EventLog = []; // This is a hack needed to update for ListBox
EventLog = new ObservableCollection<string>(log);
});
}
}
}