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;
}
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 Serilog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Needlework.Net
{
@@ -16,7 +12,7 @@ namespace Needlework.Net
{
var logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.File("Logs/debug-.log", rollingInterval: RollingInterval.Day, shared: true)
.WriteTo.File("Logs/debug-", rollingInterval: RollingInterval.Day, shared: true)
.CreateLogger();
logger.Debug("NeedleworkDotNet version: {Version}", Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0");
logger.Debug("OS description: {Description}", System.Runtime.InteropServices.RuntimeInformation.OSDescription);
@@ -25,7 +21,7 @@ namespace Needlework.Net
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;
public class OpenApiDocumentWrapper
public class Document
{
internal OpenApiDocument OpenApiDocument { get; }
@@ -14,7 +14,7 @@ public class OpenApiDocumentWrapper
public List<string> Paths => [.. OpenApiDocument.Paths.Keys];
public OpenApiDocumentWrapper(OpenApiDocument openApiDocument)
public Document(OpenApiDocument openApiDocument)
{
OpenApiDocument = openApiDocument;
var plugins = new SortedDictionary<string, List<PathOperation>>();

View File

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

View File

@@ -19,20 +19,17 @@ using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Reflection;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
namespace Needlework.Net.ViewModels.MainWindow;
public partial class MainWindowViewModel
: ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
: ObservableObject, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
{
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
[NotifyPropertyChangedFor(nameof(CurrentPage))]
[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";
[ObservableProperty] private bool _isUpdateShown = false;
@@ -42,7 +39,7 @@ public partial class MainWindowViewModel
public HttpClient HttpClient { get; }
public DialogService DialogService { get; }
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
public Document? OpenApiDocumentWrapper { get; set; }
public OpenApiDocument? HostDocument { get; set; }
[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") }
}));
SelectedMenuItem = MenuItems[0];
CurrentPage = (PageBase)MenuItems[0].Tag!;
HttpClient = httpClient;
DialogService = dialogService;
WeakReferenceMessenger.Default.RegisterAll(this);
Task.Run(FetchDataAsync);
_latestUpdateTimer.Elapsed += OnLatestUpdateTimerElapsed;
_schemaVersionTimer.Elapsed += OnSchemaVersionTimerElapsed;
_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)
{
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]
private void OpenUrl(string url)
{

View File

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

View File

@@ -1,15 +1,14 @@
using Avalonia.Collections;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.Shared;
using System.Threading.Tasks;
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 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 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);
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]
@@ -28,14 +41,4 @@ public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
{
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 CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.Shared;
using System;
using System.Linq;
@@ -23,12 +21,10 @@ public partial class EndpointViewModel : ObservableObject
public event EventHandler<string>? PathOperationSelected;
public EndpointViewModel(string endpoint, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
public EndpointViewModel(string endpoint, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Models.Document lcuSchemaDocument)
{
Endpoint = endpoint;
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x, lcuRequestViewModelLogger)));
PathOperations = new AvaloniaList<PathOperationViewModel>(lcuSchemaDocument.Plugins[endpoint].Select(x => new PathOperationViewModel(x, lcuRequestViewModelLogger, lcuSchemaDocument)));
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
}

View File

@@ -17,9 +17,9 @@ public partial class EndpointsNavigationViewModel : ObservableObject
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;
}

View File

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

View File

@@ -2,6 +2,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using Needlework.Net.Models;
using Needlework.Net.ViewModels.Shared;
using System;
using System.Linq;
@@ -19,13 +20,15 @@ public partial class EndpointsViewModel : ObservableObject
public Action<ObservableObject> OnClicked { get; }
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);
Query = new AvaloniaList<string>(plugins);
OnClicked = onClicked;
_lcuRequestViewModelLogger = lcuRequestViewModelLogger;
_lcuSchemaDocument = lcuSchemaDocument;
}
partial void OnSearchChanged(string value)
@@ -42,6 +45,6 @@ public partial class EndpointsViewModel : ObservableObject
{
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 CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.OpenApi.Models;
using Needlework.Net.Messages;
using Needlework.Net.Models;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
@@ -22,23 +21,23 @@ public partial class OperationViewModel : ObservableObject
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
public string? RequestTemplate { get; }
public OperationViewModel(OpenApiOperation operation)
public OperationViewModel(OpenApiOperation operation, Models.Document lcuSchemaDocument)
{
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);
RequestClasses = GetRequestClasses(operation.RequestBody, lcuSchemaDocument);
ResponseClasses = GetResponseClasses(operation.Responses, lcuSchemaDocument);
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
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)
{
var type = GetRequestBodyType(requestBody);
@@ -133,12 +132,12 @@ public partial class OperationViewModel : ObservableObject
return pathParameters;
}
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses)
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document lcuSchemaDocument)
{
if (responses.TryGetValue("2XX", out var response)
&& response.Content.TryGetValue("application/json", out var media))
{
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
var document = lcuSchemaDocument.OpenApiDocument;
var schema = media.Schema;
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
WalkSchema(schema, propertyClasses, document);
@@ -186,12 +185,12 @@ public partial class OperationViewModel : ObservableObject
|| type.Contains("number"));
}
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody)
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document lcuSchemaDocument)
{
if (requestBody == null) return [];
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;
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 Microsoft.Extensions.Logging;
using Needlework.Net.Models;
@@ -20,10 +19,10 @@ public partial class PathOperationViewModel : ObservableObject
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest;
public PathOperationViewModel(PathOperation pathOperation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
public PathOperationViewModel(PathOperation pathOperation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger, Document lcuSchemaDocument)
{
Path = pathOperation.Path;
Operation = new OperationViewModel(pathOperation.Operation);
Operation = new OperationViewModel(pathOperation.Operation, lcuSchemaDocument);
LcuRequest = new(() => new LcuRequestViewModel(lcuRequestViewModelLogger)
{
Method = pathOperation.Method.ToUpper()

View File

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

View File

@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Threading.Tasks;
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 _icon = icon;
[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()
{
try