refactor: use data source, remove data loading in ctor of view models

This commit is contained in:
estrogen elf
2025-05-25 12:56:40 -05:00
parent 6d1acee8df
commit c51f20a324
20 changed files with 172 additions and 130 deletions

View File

@@ -40,8 +40,6 @@ public partial class App(IServiceProvider serviceProvider) : Application
MainWindow = desktop.MainWindow; MainWindow = desktop.MainWindow;
} }
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }
} }

View File

@@ -0,0 +1,60 @@
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Readers;
using Needlework.Net.Models;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Needlework.Net
{
public class DataSource
{
private readonly ILogger<DataSource> _logger;
private readonly HttpClient _httpClient;
private Document? _lcuSchemaDocument;
private Document? _lolClientDocument;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new();
public DataSource(HttpClient httpClient, ILogger<DataSource> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<Document> GetLcuSchemaDocumentAsync()
{
await _taskCompletionSource.Task;
return _lcuSchemaDocument ?? throw new InvalidOperationException();
}
public async Task<Document> GetLolClientDocumentAsync()
{
await _taskCompletionSource.Task;
return _lolClientDocument ?? throw new InvalidOperationException();
}
public async Task InitializeAsync()
{
try
{
var reader = new OpenApiStreamReader();
var lcuSchemaStream = await _httpClient.GetStreamAsync("https://raw.githubusercontent.com/dysolix/hasagi-types/main/swagger.json");
var lcuSchemaRaw = reader.Read(lcuSchemaStream, out var _);
_lcuSchemaDocument = new Document(lcuSchemaRaw);
var lolClientStream = await _httpClient.GetStreamAsync("https://raw.githubusercontent.com/BlossomiShymae/poroschema/refs/heads/main/schemas/lolclient.json");
var lolClientRaw = reader.Read(lolClientStream, out var _);
_lolClientDocument = new Document(lolClientRaw);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize DataSource");
}
finally
{
_taskCompletionSource.SetResult(true);
}
}
}
}

View File

@@ -1,12 +1,8 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog; using Serilog;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Needlework.Net namespace Needlework.Net
{ {
@@ -16,7 +12,7 @@ namespace Needlework.Net
{ {
var logger = new LoggerConfiguration() var logger = new LoggerConfiguration()
.MinimumLevel.Debug() .MinimumLevel.Debug()
.WriteTo.File("Logs/debug-.log", rollingInterval: RollingInterval.Day, shared: true) .WriteTo.File("Logs/debug-", rollingInterval: RollingInterval.Day, shared: true)
.CreateLogger(); .CreateLogger();
logger.Debug("NeedleworkDotNet version: {Version}", Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0"); logger.Debug("NeedleworkDotNet version: {Version}", Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0");
logger.Debug("OS description: {Description}", System.Runtime.InteropServices.RuntimeInformation.OSDescription); logger.Debug("OS description: {Description}", System.Runtime.InteropServices.RuntimeInformation.OSDescription);
@@ -25,7 +21,7 @@ namespace Needlework.Net
public static void LogFatal(UnhandledExceptionEventArgs e) public static void LogFatal(UnhandledExceptionEventArgs e)
{ {
File.WriteAllText($"Logs/fatal-{DateTime.Now:HHmmssfff}.log", e.ExceptionObject.ToString()); File.WriteAllText($"Logs/fatal-{DateTime.Now:HHmmssfff}", e.ExceptionObject.ToString());
} }
} }
} }

View File

@@ -1,9 +0,0 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using Needlework.Net.Models;
namespace Needlework.Net.Messages
{
public class DataReadyMessage(OpenApiDocumentWrapper wrapper) : ValueChangedMessage<OpenApiDocumentWrapper>(wrapper)
{
}
}

View File

@@ -1,9 +0,0 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using Needlework.Net.Models;
namespace Needlework.Net.Messages
{
public class DataRequestMessage : RequestMessage<OpenApiDocumentWrapper>
{
}
}

View File

@@ -1,9 +0,0 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.OpenApi.Models;
namespace Needlework.Net.Messages
{
public class HostDocumentRequestMessage : RequestMessage<OpenApiDocument>
{
}
}

View File

@@ -4,7 +4,7 @@ using Microsoft.OpenApi.Models;
namespace Needlework.Net.Models; namespace Needlework.Net.Models;
public class OpenApiDocumentWrapper public class Document
{ {
internal OpenApiDocument OpenApiDocument { get; } internal OpenApiDocument OpenApiDocument { get; }
@@ -14,7 +14,7 @@ public class OpenApiDocumentWrapper
public List<string> Paths => [.. OpenApiDocument.Paths.Keys]; public List<string> Paths => [.. OpenApiDocument.Paths.Keys];
public OpenApiDocumentWrapper(OpenApiDocument openApiDocument) public Document(OpenApiDocument openApiDocument)
{ {
OpenApiDocument = openApiDocument; OpenApiDocument = openApiDocument;
var plugins = new SortedDictionary<string, List<PathOperation>>(); var plugins = new SortedDictionary<string, List<PathOperation>>();

View File

@@ -6,10 +6,8 @@ using Needlework.Net.ViewModels.MainWindow;
using Needlework.Net.ViewModels.Pages; using Needlework.Net.ViewModels.Pages;
using Projektanker.Icons.Avalonia; using Projektanker.Icons.Avalonia;
using Projektanker.Icons.Avalonia.FontAwesome; using Projektanker.Icons.Avalonia.FontAwesome;
using Serilog;
using System; using System;
using System.IO; using System.Threading.Tasks;
using System.Reflection;
namespace Needlework.Net; namespace Needlework.Net;
@@ -32,23 +30,32 @@ class Program
{ {
IconProvider.Current IconProvider.Current
.Register<FontAwesomeIconProvider>(); .Register<FontAwesomeIconProvider>();
var services = BuildServices();
Task.Run(async () => await InitializeDataSourceAsync(services));
return AppBuilder.Configure(() => new App(BuildServices())) return AppBuilder.Configure(() => new App(services))
.UsePlatformDetect() .UsePlatformDetect()
.WithInterFont() .WithInterFont()
.LogToTrace() .LogToTrace()
.With(new Win32PlatformOptions .With(new Win32PlatformOptions
{ {
CompositionMode = [ Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition ] CompositionMode = [Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition]
}); });
} }
private static async Task InitializeDataSourceAsync(IServiceProvider services)
{
var dataSource = services.GetRequiredService<DataSource>();
await dataSource.InitializeAsync();
}
private static IServiceProvider BuildServices() private static IServiceProvider BuildServices()
{ {
var builder = new ServiceCollection(); var builder = new ServiceCollection();
builder.AddSingleton<MainWindowViewModel>(); builder.AddSingleton<MainWindowViewModel>();
builder.AddSingleton<DialogService>(); builder.AddSingleton<DialogService>();
builder.AddSingleton<DataSource>();
builder.AddSingletonsFromAssemblies<PageBase>(); builder.AddSingletonsFromAssemblies<PageBase>();
builder.AddHttpClient(); builder.AddHttpClient();
builder.AddLogging(Logger.Setup); builder.AddLogging(Logger.Setup);

View File

@@ -19,20 +19,17 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Reflection; using System.Reflection;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers; using System.Timers;
namespace Needlework.Net.ViewModels.MainWindow; namespace Needlework.Net.ViewModels.MainWindow;
public partial class MainWindowViewModel public partial class MainWindowViewModel
: ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage> : ObservableObject, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
{ {
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; } public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
[NotifyPropertyChangedFor(nameof(CurrentPage))]
[ObservableProperty] private NavigationViewItem _selectedMenuItem; [ObservableProperty] private NavigationViewItem _selectedMenuItem;
public PageBase CurrentPage => (PageBase)SelectedMenuItem.Tag!; [ObservableProperty] private PageBase _currentPage;
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0"; public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
[ObservableProperty] private bool _isUpdateShown = false; [ObservableProperty] private bool _isUpdateShown = false;
@@ -42,7 +39,7 @@ public partial class MainWindowViewModel
public HttpClient HttpClient { get; } public HttpClient HttpClient { get; }
public DialogService DialogService { get; } public DialogService DialogService { get; }
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; } public Document? OpenApiDocumentWrapper { get; set; }
public OpenApiDocument? HostDocument { get; set; } public OpenApiDocument? HostDocument { get; set; }
[ObservableProperty] private bool _isBusy = true; [ObservableProperty] private bool _isBusy = true;
@@ -78,14 +75,13 @@ public partial class MainWindowViewModel
IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") } IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") }
})); }));
SelectedMenuItem = MenuItems[0]; SelectedMenuItem = MenuItems[0];
CurrentPage = (PageBase)MenuItems[0].Tag!;
HttpClient = httpClient; HttpClient = httpClient;
DialogService = dialogService; DialogService = dialogService;
WeakReferenceMessenger.Default.RegisterAll(this); WeakReferenceMessenger.Default.RegisterAll(this);
Task.Run(FetchDataAsync);
_latestUpdateTimer.Elapsed += OnLatestUpdateTimerElapsed; _latestUpdateTimer.Elapsed += OnLatestUpdateTimerElapsed;
_schemaVersionTimer.Elapsed += OnSchemaVersionTimerElapsed; _schemaVersionTimer.Elapsed += OnSchemaVersionTimerElapsed;
_latestUpdateTimer.Start(); _latestUpdateTimer.Start();
@@ -95,6 +91,18 @@ public partial class MainWindowViewModel
} }
partial void OnSelectedMenuItemChanged(NavigationViewItem value)
{
if (value.Tag is PageBase page)
{
CurrentPage = page;
if (!page.IsInitialized)
{
Task.Run(page.InitializeAsync);
}
}
}
private async void OnSchemaVersionTimerElapsed(object? sender, ElapsedEventArgs? e) private async void OnSchemaVersionTimerElapsed(object? sender, ElapsedEventArgs? e)
{ {
if (OpenApiDocumentWrapper == null) return; if (OpenApiDocumentWrapper == null) return;
@@ -177,34 +185,6 @@ public partial class MainWindowViewModel
} }
} }
private async Task FetchDataAsync()
{
try
{
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
HostDocument = document;
var handler = new OpenApiDocumentWrapper(document);
OpenApiDocumentWrapper = handler;
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
IsBusy = false;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Failed to fetch OpenAPI data");
}
}
public void Receive(DataRequestMessage message)
{
message.Reply(OpenApiDocumentWrapper!);
}
public void Receive(HostDocumentRequestMessage message)
{
message.Reply(HostDocument!);
}
[RelayCommand] [RelayCommand]
private void OpenUrl(string url) private void OpenUrl(string url)
{ {

View File

@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using System.Diagnostics; using System.Diagnostics;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
namespace Needlework.Net.ViewModels.Pages; namespace Needlework.Net.ViewModels.Pages;
@@ -13,6 +14,12 @@ public partial class AboutViewModel : PageBase
HttpClient = httpClient; HttpClient = httpClient;
} }
public override Task InitializeAsync()
{
IsInitialized = true;
return Task.CompletedTask;
}
[RelayCommand] [RelayCommand]
private void OpenUrl(string url) private void OpenUrl(string url)
{ {

View File

@@ -1,15 +1,14 @@
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.Shared; using Needlework.Net.ViewModels.Shared;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Needlework.Net.ViewModels.Pages; namespace Needlework.Net.ViewModels.Pages;
public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage> public partial class ConsoleViewModel : PageBase
{ {
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]); public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>(); public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
@@ -17,10 +16,24 @@ public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
[ObservableProperty] private bool _isBusy = true; [ObservableProperty] private bool _isBusy = true;
[ObservableProperty] private LcuRequestViewModel _lcuRequest; [ObservableProperty] private LcuRequestViewModel _lcuRequest;
public ConsoleViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) : base("Console", "terminal", -200) private readonly DataSource _dataSource;
public ConsoleViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, DataSource dataSource) : base("Console", "terminal", -200)
{ {
_lcuRequest = new(lcuRequestViewModelLogger); _lcuRequest = new(lcuRequestViewModelLogger);
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this); _dataSource = dataSource;
}
public override async Task InitializeAsync()
{
var document = await _dataSource.GetLolClientDocumentAsync();
Dispatcher.UIThread.Invoke(() =>
{
RequestPaths.Clear();
RequestPaths.AddRange(document.Paths);
});
IsBusy = false;
IsInitialized = true;
} }
[RelayCommand] [RelayCommand]
@@ -28,14 +41,4 @@ public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
{ {
await LcuRequest.ExecuteAsync(); await LcuRequest.ExecuteAsync();
} }
public void Receive(DataReadyMessage message)
{
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
{
RequestPaths.Clear();
RequestPaths.AddRange(message.Value.Paths);
IsBusy = false;
});
}
} }

View File

@@ -1,8 +1,6 @@
using Avalonia.Collections; using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.Shared; using Needlework.Net.ViewModels.Shared;
using System; using System;
using System.Linq; using System.Linq;
@@ -23,12 +21,10 @@ public partial class EndpointViewModel : ObservableObject
public event EventHandler<string>? PathOperationSelected; public event EventHandler<string>? PathOperationSelected;
public EndpointViewModel(string endpoint, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) public EndpointViewModel(string endpoint, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Models.Document lcuSchemaDocument)
{ {
Endpoint = endpoint; Endpoint = endpoint;
PathOperations = new AvaloniaList<PathOperationViewModel>(lcuSchemaDocument.Plugins[endpoint].Select(x => new PathOperationViewModel(x, lcuRequestViewModelLogger, lcuSchemaDocument)));
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x, lcuRequestViewModelLogger)));
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations); FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
} }

View File

@@ -17,9 +17,9 @@ public partial class EndpointsNavigationViewModel : ObservableObject
private readonly Action<string?, Guid> _onEndpointNavigation; private readonly Action<string?, Guid> _onEndpointNavigation;
public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Models.Document lcuSchemaDocument)
{ {
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked, lcuRequestViewModelLogger); _activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked, lcuRequestViewModelLogger, lcuSchemaDocument);
_onEndpointNavigation = onEndpointNavigation; _onEndpointNavigation = onEndpointNavigation;
} }

View File

@@ -5,13 +5,13 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.Shared; using Needlework.Net.ViewModels.Shared;
using System; using System;
using System.Threading.Tasks;
namespace Needlework.Net.ViewModels.Pages.Endpoints; namespace Needlework.Net.ViewModels.Pages.Endpoints;
public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessage> public partial class EndpointsTabViewModel : PageBase
{ {
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>(); public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
public IAvaloniaList<EndpointItem> Endpoints { get; } = new AvaloniaList<EndpointItem>(); public IAvaloniaList<EndpointItem> Endpoints { get; } = new AvaloniaList<EndpointItem>();
@@ -19,28 +19,31 @@ public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessa
[ObservableProperty] private bool _isBusy = true; [ObservableProperty] private bool _isBusy = true;
private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger; private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger;
private readonly DataSource _dataSource;
public EndpointsTabViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) : base("Endpoints", "list-alt", -500) public EndpointsTabViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, DataSource dataSource) : base("Endpoints", "list-alt", -500)
{ {
_lcuRequestViewModelLogger = lcuRequestViewModelLogger; _lcuRequestViewModelLogger = lcuRequestViewModelLogger;
_dataSource = dataSource;
WeakReferenceMessenger.Default.RegisterAll(this); WeakReferenceMessenger.Default.RegisterAll(this);
} }
public override async Task InitializeAsync()
public void Receive(DataReadyMessage message)
{ {
IsBusy = false; var document = await _dataSource.GetLcuSchemaDocumentAsync();
Plugins.Clear(); Plugins.Clear();
Plugins.AddRange(message.Value.Plugins.Keys); Plugins.AddRange(document.Plugins.Keys);
await Dispatcher.UIThread.Invoke(AddEndpoint);
Dispatcher.UIThread.Post(AddEndpoint); IsBusy = false;
IsInitialized = true;
} }
[RelayCommand] [RelayCommand]
private void AddEndpoint() private async Task AddEndpoint()
{ {
var lcuSchemaDocument = await _dataSource.GetLcuSchemaDocumentAsync();
Endpoints.Add(new() Endpoints.Add(new()
{ {
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation, _lcuRequestViewModelLogger), Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation, _lcuRequestViewModelLogger, lcuSchemaDocument),
Selected = true Selected = true
}); });
} }

View File

@@ -2,6 +2,7 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
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.Linq; using System.Linq;
@@ -19,13 +20,15 @@ public partial class EndpointsViewModel : ObservableObject
public Action<ObservableObject> OnClicked { get; } public Action<ObservableObject> OnClicked { get; }
private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger; private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger;
private readonly Document _lcuSchemaDocument;
public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Models.Document lcuSchemaDocument)
{ {
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; _lcuRequestViewModelLogger = lcuRequestViewModelLogger;
_lcuSchemaDocument = lcuSchemaDocument;
} }
partial void OnSearchChanged(string value) partial void OnSearchChanged(string value)
@@ -42,6 +45,6 @@ public partial class EndpointsViewModel : ObservableObject
{ {
if (string.IsNullOrEmpty(value)) return; if (string.IsNullOrEmpty(value)) return;
OnClicked.Invoke(new EndpointViewModel(value, _lcuRequestViewModelLogger)); OnClicked.Invoke(new EndpointViewModel(value, _lcuRequestViewModelLogger, _lcuSchemaDocument));
} }
} }

View File

@@ -1,8 +1,7 @@
using Avalonia.Collections; using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Needlework.Net.Messages; using Needlework.Net.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@@ -22,23 +21,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) public OperationViewModel(OpenApiOperation operation, Models.Document lcuSchemaDocument)
{ {
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); RequestClasses = GetRequestClasses(operation.RequestBody, lcuSchemaDocument);
ResponseClasses = GetResponseClasses(operation.Responses); ResponseClasses = GetResponseClasses(operation.Responses, lcuSchemaDocument);
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); RequestTemplate = GetRequestTemplate(operation.RequestBody, lcuSchemaDocument);
} }
private string? GetRequestTemplate(OpenApiRequestBody? requestBody) private string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document lcuSchemaDocument)
{ {
var requestClasses = GetRequestClasses(requestBody); var requestClasses = GetRequestClasses(requestBody, lcuSchemaDocument);
if (requestClasses.Count == 0) if (requestClasses.Count == 0)
{ {
var type = GetRequestBodyType(requestBody); var type = GetRequestBodyType(requestBody);
@@ -133,12 +132,12 @@ public partial class OperationViewModel : ObservableObject
return pathParameters; return pathParameters;
} }
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses) private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document lcuSchemaDocument)
{ {
if (responses.TryGetValue("2XX", out var response) if (responses.TryGetValue("2XX", out var response)
&& response.Content.TryGetValue("application/json", out var media)) && response.Content.TryGetValue("application/json", out var media))
{ {
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response; var document = lcuSchemaDocument.OpenApiDocument;
var schema = media.Schema; var schema = media.Schema;
AvaloniaList<PropertyClassViewModel> propertyClasses = []; AvaloniaList<PropertyClassViewModel> propertyClasses = [];
WalkSchema(schema, propertyClasses, document); WalkSchema(schema, propertyClasses, document);
@@ -186,12 +185,12 @@ public partial class OperationViewModel : ObservableObject
|| type.Contains("number")); || type.Contains("number"));
} }
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody) private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document lcuSchemaDocument)
{ {
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 = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response; var document = lcuSchemaDocument.OpenApiDocument;
var schema = media.Schema; var schema = media.Schema;
if (schema == null) return []; if (schema == null) return [];

View File

@@ -1,5 +1,4 @@
using Avalonia.Controls; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Needlework.Net.Models; using Needlework.Net.Models;
@@ -20,10 +19,10 @@ public partial class PathOperationViewModel : ObservableObject
[ObservableProperty] private bool _isBusy; [ObservableProperty] private bool _isBusy;
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest; [ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest;
public PathOperationViewModel(PathOperation pathOperation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) public PathOperationViewModel(PathOperation pathOperation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Document lcuSchemaDocument)
{ {
Path = pathOperation.Path; Path = pathOperation.Path;
Operation = new OperationViewModel(pathOperation.Operation); Operation = new OperationViewModel(pathOperation.Operation, lcuSchemaDocument);
LcuRequest = new(() => new LcuRequestViewModel(lcuRequestViewModelLogger) LcuRequest = new(() => new LcuRequestViewModel(lcuRequestViewModelLogger)
{ {
Method = pathOperation.Method.ToUpper() Method = pathOperation.Method.ToUpper()

View File

@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
namespace Needlework.Net.ViewModels.Pages; namespace Needlework.Net.ViewModels.Pages;
@@ -14,6 +15,12 @@ public partial class HomeViewModel : PageBase
public HomeViewModel() : base("Home", "home", int.MinValue) { } public HomeViewModel() : base("Home", "home", int.MinValue) { }
public override Task InitializeAsync()
{
IsInitialized = true;
return Task.CompletedTask;
}
[RelayCommand] [RelayCommand]
private void OpenUrl(string url) private void OpenUrl(string url)
{ {
@@ -23,4 +30,5 @@ public partial class HomeViewModel : PageBase
}; };
process.Start(); process.Start();
} }
} }

View File

@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using System.Threading.Tasks;
namespace Needlework.Net.ViewModels.Pages; namespace Needlework.Net.ViewModels.Pages;
@@ -8,4 +9,7 @@ public abstract partial class PageBase(string displayName, string icon, int inde
[ObservableProperty] private string _displayName = displayName; [ObservableProperty] private string _displayName = displayName;
[ObservableProperty] private string _icon = icon; [ObservableProperty] private string _icon = icon;
[ObservableProperty] private int _index = index; [ObservableProperty] private int _index = index;
[ObservableProperty] private bool _isInitialized;
public abstract Task InitializeAsync();
} }

View File

@@ -59,6 +59,12 @@ public partial class WebsocketViewModel : PageBase
}); });
} }
public override Task InitializeAsync()
{
IsInitialized = true;
return Task.CompletedTask;
}
private async Task InitializeEventTypes() private async Task InitializeEventTypes()
{ {
try try