mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 18:20:47 +01:00
Refactor folder structure
This commit is contained in:
@@ -3,8 +3,8 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
using Needlework.Net.Views;
|
using Needlework.Net.Views.MainWindow;
|
||||||
using System;
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ public partial class App(IServiceProvider serviceProvider) : Application
|
|||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
desktop.MainWindow = new MainWindow()
|
desktop.MainWindow = new MainWindowView()
|
||||||
{
|
{
|
||||||
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
|
|
||||||
namespace Needlework.Net.Messages
|
namespace Needlework.Net.Messages
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,7 +52,10 @@
|
|||||||
<Compile Update="Controls\BusyArea.axaml.cs">
|
<Compile Update="Controls\BusyArea.axaml.cs">
|
||||||
<DependentUpon>BusyArea.axaml</DependentUpon>
|
<DependentUpon>BusyArea.axaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Update="Views\EndpointView.axaml.cs">
|
<Compile Update="Views\MainWindow\MainWindowView.axaml.cs">
|
||||||
|
<DependentUpon>MainWindowView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Views\Pages\EndpointView.axaml.cs">
|
||||||
<DependentUpon>EndpointView.axaml</DependentUpon>
|
<DependentUpon>EndpointView.axaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
using Needlework.Net.Services;
|
using Needlework.Net.Services;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
|
using Needlework.Net.ViewModels.Pages;
|
||||||
using Projektanker.Icons.Avalonia;
|
using Projektanker.Icons.Avalonia;
|
||||||
using Projektanker.Icons.Avalonia.FontAwesome;
|
using Projektanker.Icons.Avalonia.FontAwesome;
|
||||||
using System;
|
using System;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ using Avalonia.Controls.Templates;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Needlework.Net
|
namespace Needlework.Net
|
||||||
{
|
{
|
||||||
@@ -12,17 +14,21 @@ namespace Needlework.Net
|
|||||||
|
|
||||||
public Control Build(object? data)
|
public Control Build(object? data)
|
||||||
{
|
{
|
||||||
var fullName = data?.GetType().FullName;
|
var name = data?.GetType().Name;
|
||||||
if (fullName is null)
|
if (name is null)
|
||||||
{
|
{
|
||||||
return new TextBlock { Text = "Data is null or has no name." };
|
return new TextBlock { Text = "Data is null or has no name." };
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = fullName.Replace("ViewModel", "View");
|
name = name.Replace("ViewModel", "View");
|
||||||
var type = Type.GetType(name);
|
var type = Assembly.GetExecutingAssembly()
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t => t.Name == name)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (type is null)
|
if (type is null)
|
||||||
{
|
{
|
||||||
return new TextBlock { Text = $"No View For {name}." };
|
return new TextBlock { Text = $"No view for {name}." };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_controlCache.TryGetValue(data!, out var res))
|
if (!_controlCache.TryGetValue(data!, out var res))
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class AboutViewModel : PageBase
|
|
||||||
{
|
|
||||||
public HttpClient HttpClient { get; }
|
|
||||||
|
|
||||||
public AboutViewModel(HttpClient httpClient) : base("About", "info-circle")
|
|
||||||
{
|
|
||||||
HttpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void OpenUrl(string url)
|
|
||||||
{
|
|
||||||
var process = new Process()
|
|
||||||
{
|
|
||||||
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
|
||||||
};
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using BlossomiShymae.GrrrLCU;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using System;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
|
|
||||||
{
|
|
||||||
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
|
||||||
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
|
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
|
||||||
[ObservableProperty] private bool _isRequestBusy = false;
|
|
||||||
[ObservableProperty] private string? _requestMethodSelected = "GET";
|
|
||||||
[ObservableProperty] private string? _requestPath = null;
|
|
||||||
[ObservableProperty] private string? _requestBody = null;
|
|
||||||
[ObservableProperty] private string? _responsePath = null;
|
|
||||||
[ObservableProperty] private string? _responseStatus = null;
|
|
||||||
[ObservableProperty] private string? _responseAuthorization = null;
|
|
||||||
|
|
||||||
public ConsoleViewModel() : base("Console", "terminal", -200)
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private async Task SendRequest()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IsRequestBusy = true;
|
|
||||||
if (string.IsNullOrEmpty(RequestPath)) throw new Exception("Path is empty.");
|
|
||||||
|
|
||||||
var method = RequestMethodSelected 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."),
|
|
||||||
};
|
|
||||||
|
|
||||||
var processInfo = ProcessFinder.Get();
|
|
||||||
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "ConsoleRequestEditor").Response;
|
|
||||||
var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
|
||||||
var client = Connector.GetLcuHttpClientInstance();
|
|
||||||
var response = await client.SendAsync(new(method, RequestPath) { Content = content });
|
|
||||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body));
|
|
||||||
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
|
|
||||||
}
|
|
||||||
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(body), nameof(ConsoleViewModel));
|
|
||||||
|
|
||||||
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
|
|
||||||
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
|
|
||||||
ResponseAuthorization = $"Basic {riotAuthentication.Value}";
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
|
||||||
ResponseStatus = null;
|
|
||||||
ResponsePath = null;
|
|
||||||
ResponseAuthorization = null;
|
|
||||||
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsRequestBusy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(DataReadyMessage message)
|
|
||||||
{
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
|
||||||
{
|
|
||||||
RequestPaths.Clear();
|
|
||||||
RequestPaths.AddRange(message.Value.Paths);
|
|
||||||
IsBusy = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class EndpointViewModel : ObservableObject
|
|
||||||
{
|
|
||||||
public string Endpoint { get; }
|
|
||||||
public string Title => Endpoint;
|
|
||||||
|
|
||||||
|
|
||||||
public IAvaloniaReadOnlyList<PathOperationViewModel> PathOperations { get; }
|
|
||||||
[ObservableProperty] private PathOperationViewModel? _selectedPathOperation;
|
|
||||||
|
|
||||||
[ObservableProperty] private string? _search;
|
|
||||||
public IAvaloniaList<PathOperationViewModel> FilteredPathOperations { get; }
|
|
||||||
|
|
||||||
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)));
|
|
||||||
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnSearchChanged(string? value)
|
|
||||||
{
|
|
||||||
FilteredPathOperations.Clear();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
FilteredPathOperations.AddRange(PathOperations);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FilteredPathOperations.AddRange(PathOperations.Where(o => o.Path.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnSelectedPathOperationChanged(PathOperationViewModel? value)
|
|
||||||
{
|
|
||||||
if (value == null) return;
|
|
||||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(value.Operation.RequestTemplate ?? string.Empty, "EndpointRequestEditor")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class EndpointsContainerViewModel : PageBase
|
|
||||||
{
|
|
||||||
[ObservableProperty] private ObservableObject _activeViewModel;
|
|
||||||
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
|
||||||
[ObservableProperty] private string _title = string.Empty;
|
|
||||||
|
|
||||||
public EndpointsContainerViewModel(HttpClient httpClient) : base("Endpoints", "list-alt", -500)
|
|
||||||
{
|
|
||||||
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(httpClient, OnClicked);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClicked(ObservableObject viewModel)
|
|
||||||
{
|
|
||||||
ActiveViewModel = viewModel;
|
|
||||||
if (viewModel is EndpointViewModel endpoint) Title = endpoint.Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void GoBack()
|
|
||||||
{
|
|
||||||
ActiveViewModel = EndpointsViewModel;
|
|
||||||
Title = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class EndpointsViewModel : ObservableObject, IRecipient<DataReadyMessage>
|
|
||||||
{
|
|
||||||
public HttpClient HttpClient { get; }
|
|
||||||
|
|
||||||
public string Title => "Endpoints";
|
|
||||||
public Action<ObservableObject> OnClicked;
|
|
||||||
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
|
|
||||||
public IAvaloniaList<string> Query { get; } = new AvaloniaList<string>();
|
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
|
||||||
[ObservableProperty] private string _search = string.Empty;
|
|
||||||
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
|
||||||
|
|
||||||
public EndpointsViewModel(HttpClient httpClient, Action<ObservableObject> onClicked)
|
|
||||||
{
|
|
||||||
HttpClient = httpClient;
|
|
||||||
OnClicked = onClicked;
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(DataReadyMessage message)
|
|
||||||
{
|
|
||||||
IsBusy = false;
|
|
||||||
Plugins.Clear();
|
|
||||||
Plugins.AddRange(message.Value.Plugins.Keys);
|
|
||||||
Query.Clear();
|
|
||||||
Query.AddRange(Plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnSearchChanged(string value)
|
|
||||||
{
|
|
||||||
Query.Clear();
|
|
||||||
if (!string.IsNullOrEmpty(Search))
|
|
||||||
Query.AddRange(Plugins.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
|
||||||
else
|
|
||||||
Query.AddRange(Plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void OpenEndpoint(string? value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(value)) return;
|
|
||||||
|
|
||||||
OnClicked.Invoke(new EndpointViewModel(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using BlossomiShymae.GrrrLCU;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public class EventViewModel : ObservableObject
|
|
||||||
{
|
|
||||||
public string Time { get; }
|
|
||||||
public string Type { get; }
|
|
||||||
public string Uri { get; }
|
|
||||||
|
|
||||||
public string Key => $"{Time} {Type} {Uri}";
|
|
||||||
|
|
||||||
public EventViewModel(EventData eventData)
|
|
||||||
{
|
|
||||||
Time = $"{DateTime.Now:HH:mm:ss.fff}";
|
|
||||||
Type = eventData?.EventType.ToUpper() ?? string.Empty;
|
|
||||||
Uri = eventData?.Uri ?? string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class HomeViewModel : PageBase
|
|
||||||
{
|
|
||||||
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void OpenUrl(string url)
|
|
||||||
{
|
|
||||||
var process = new Process()
|
|
||||||
{
|
|
||||||
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
|
||||||
};
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class InfoBarViewModel : ObservableObject
|
|
||||||
{
|
|
||||||
[ObservableProperty] private string _title;
|
|
||||||
[ObservableProperty] private bool _isOpen;
|
|
||||||
[ObservableProperty] private string _message;
|
|
||||||
[ObservableProperty] private InfoBarSeverity _severity;
|
|
||||||
[ObservableProperty] private TimeSpan _duration;
|
|
||||||
[ObservableProperty] private Control? _actionButton;
|
|
||||||
|
|
||||||
public InfoBarViewModel(string title, bool isOpen, string message, InfoBarSeverity severity, TimeSpan duration, Control? actionButton = null)
|
|
||||||
{
|
|
||||||
_title = title;
|
|
||||||
_isOpen = isOpen;
|
|
||||||
_message = message;
|
|
||||||
_severity = severity;
|
|
||||||
_duration = duration;
|
|
||||||
_actionButton = actionButton;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
Needlework.Net/ViewModels/MainWindow/InfoBarViewModel.cs
Normal file
26
Needlework.Net/ViewModels/MainWindow/InfoBarViewModel.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.MainWindow;
|
||||||
|
|
||||||
|
public partial class InfoBarViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty] private string _title;
|
||||||
|
[ObservableProperty] private bool _isOpen;
|
||||||
|
[ObservableProperty] private string _message;
|
||||||
|
[ObservableProperty] private InfoBarSeverity _severity;
|
||||||
|
[ObservableProperty] private TimeSpan _duration;
|
||||||
|
[ObservableProperty] private Control? _actionButton;
|
||||||
|
|
||||||
|
public InfoBarViewModel(string title, bool isOpen, string message, InfoBarSeverity severity, TimeSpan duration, Control? actionButton = null)
|
||||||
|
{
|
||||||
|
_title = title;
|
||||||
|
_isOpen = isOpen;
|
||||||
|
_message = message;
|
||||||
|
_severity = severity;
|
||||||
|
_duration = duration;
|
||||||
|
_actionButton = actionButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
157
Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs
Normal file
157
Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using Needlework.Net.Models;
|
||||||
|
using Needlework.Net.Services;
|
||||||
|
using Needlework.Net.ViewModels.Pages;
|
||||||
|
using Needlework.Net.Views.MainWindow;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.MainWindow;
|
||||||
|
|
||||||
|
public partial class MainWindowViewModel
|
||||||
|
: ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
|
||||||
|
{
|
||||||
|
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
|
||||||
|
[NotifyPropertyChangedFor(nameof(CurrentPage))]
|
||||||
|
[ObservableProperty] private NavigationViewItem _selectedMenuItem;
|
||||||
|
public PageBase CurrentPage => (PageBase)SelectedMenuItem.Tag!;
|
||||||
|
|
||||||
|
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||||
|
[ObservableProperty] private bool _isUpdateShown = false;
|
||||||
|
|
||||||
|
public HttpClient HttpClient { get; }
|
||||||
|
public DialogService DialogService { get; }
|
||||||
|
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
|
||||||
|
public OpenApiDocument? HostDocument { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
|
||||||
|
[ObservableProperty] private ObservableCollection<InfoBarViewModel> _infoBarItems = [];
|
||||||
|
|
||||||
|
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService)
|
||||||
|
{
|
||||||
|
MenuItems = new AvaloniaList<NavigationViewItem>(pages
|
||||||
|
.OrderBy(p => p.Index)
|
||||||
|
.ThenBy(p => p.DisplayName)
|
||||||
|
.Select(p => new NavigationViewItem()
|
||||||
|
{
|
||||||
|
Content = p.DisplayName,
|
||||||
|
Tag = p,
|
||||||
|
IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") }
|
||||||
|
}));
|
||||||
|
SelectedMenuItem = MenuItems[0];
|
||||||
|
|
||||||
|
HttpClient = httpClient;
|
||||||
|
DialogService = dialogService;
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||||
|
|
||||||
|
Task.Run(FetchDataAsync);
|
||||||
|
new Thread(ProcessEvents) { IsBackground = true }.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessEvents(object? obj)
|
||||||
|
{
|
||||||
|
while (!IsUpdateShown)
|
||||||
|
{
|
||||||
|
Task.Run(CheckLatestVersionAsync);
|
||||||
|
|
||||||
|
Thread.Sleep(TimeSpan.FromSeconds(60));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckLatestVersionAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/BlossomiShymae/Needlework.Net/releases/latest");
|
||||||
|
request.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("Needlework.Net", Version));
|
||||||
|
|
||||||
|
var response = await HttpClient.SendAsync(request);
|
||||||
|
var release = await response.Content.ReadFromJsonAsync<GithubRelease>();
|
||||||
|
if (release == null) return;
|
||||||
|
|
||||||
|
var currentVersion = int.Parse(Version.Replace(".", ""));
|
||||||
|
|
||||||
|
if (release.IsLatest(currentVersion))
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Post(async () =>
|
||||||
|
{
|
||||||
|
await ShowInfoBarAsync(new("Needlework.Net Update", true, $"There is a new version available: {release.TagName}.", InfoBarSeverity.Informational, TimeSpan.FromSeconds(10), new Avalonia.Controls.Button()
|
||||||
|
{
|
||||||
|
Command = OpenUrlCommand,
|
||||||
|
CommandParameter = "https://github.com/BlossomiShymae/Needlework.Net/releases",
|
||||||
|
Content = "Download"
|
||||||
|
}));
|
||||||
|
IsUpdateShown = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FetchDataAsync()
|
||||||
|
{
|
||||||
|
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
||||||
|
HostDocument = document;
|
||||||
|
var handler = new OpenApiDocumentWrapper(document);
|
||||||
|
OpenApiDocumentWrapper = handler;
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(DataRequestMessage message)
|
||||||
|
{
|
||||||
|
message.Reply(OpenApiDocumentWrapper!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(HostDocumentRequestMessage message)
|
||||||
|
{
|
||||||
|
message.Reply(HostDocument!);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenUrl(string url)
|
||||||
|
{
|
||||||
|
var process = new Process()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo(url)
|
||||||
|
{
|
||||||
|
UseShellExecute = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(InfoBarUpdateMessage message)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Post(async () => await ShowInfoBarAsync(message.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowInfoBarAsync(InfoBarViewModel vm)
|
||||||
|
{
|
||||||
|
InfoBarItems.Add(vm);
|
||||||
|
await Task.Delay(vm.Duration);
|
||||||
|
InfoBarItems.Remove(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(OopsiesDialogRequestedMessage message)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () => await DialogService.ShowAsync<OopsiesDialog>(message.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using Needlework.Net.Models;
|
|
||||||
using Needlework.Net.Services;
|
|
||||||
using Needlework.Net.Views;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class MainWindowViewModel
|
|
||||||
: ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
|
|
||||||
{
|
|
||||||
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
|
|
||||||
[NotifyPropertyChangedFor(nameof(CurrentPage))]
|
|
||||||
[ObservableProperty] private NavigationViewItem _selectedMenuItem;
|
|
||||||
public PageBase CurrentPage => (PageBase)SelectedMenuItem.Tag!;
|
|
||||||
|
|
||||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
|
||||||
[ObservableProperty] private bool _isUpdateShown = false;
|
|
||||||
|
|
||||||
public HttpClient HttpClient { get; }
|
|
||||||
public DialogService DialogService { get; }
|
|
||||||
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
|
|
||||||
public OpenApiDocument? HostDocument { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
|
||||||
|
|
||||||
[ObservableProperty] private ObservableCollection<InfoBarViewModel> _infoBarItems = [];
|
|
||||||
|
|
||||||
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService)
|
|
||||||
{
|
|
||||||
MenuItems = new AvaloniaList<NavigationViewItem>(pages
|
|
||||||
.OrderBy(p => p.Index)
|
|
||||||
.ThenBy(p => p.DisplayName)
|
|
||||||
.Select(p => new NavigationViewItem()
|
|
||||||
{
|
|
||||||
Content = p.DisplayName,
|
|
||||||
Tag = p,
|
|
||||||
IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") }
|
|
||||||
}));
|
|
||||||
SelectedMenuItem = MenuItems[0];
|
|
||||||
|
|
||||||
HttpClient = httpClient;
|
|
||||||
DialogService = dialogService;
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
|
||||||
|
|
||||||
Task.Run(FetchDataAsync);
|
|
||||||
new Thread(ProcessEvents) { IsBackground = true }.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessEvents(object? obj)
|
|
||||||
{
|
|
||||||
while (!IsUpdateShown)
|
|
||||||
{
|
|
||||||
Task.Run(CheckLatestVersionAsync);
|
|
||||||
|
|
||||||
Thread.Sleep(TimeSpan.FromSeconds(60));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CheckLatestVersionAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/BlossomiShymae/Needlework.Net/releases/latest");
|
|
||||||
request.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("Needlework.Net", Version));
|
|
||||||
|
|
||||||
var response = await HttpClient.SendAsync(request);
|
|
||||||
var release = await response.Content.ReadFromJsonAsync<GithubRelease>();
|
|
||||||
if (release == null) return;
|
|
||||||
|
|
||||||
var currentVersion = int.Parse(Version.Replace(".", ""));
|
|
||||||
|
|
||||||
if (release.IsLatest(currentVersion))
|
|
||||||
{
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ShowInfoBarAsync(new("Needlework.Net Update", true, $"There is a new version available: {release.TagName}.", InfoBarSeverity.Informational, TimeSpan.FromSeconds(10), new Avalonia.Controls.Button()
|
|
||||||
{
|
|
||||||
Command = OpenUrlCommand,
|
|
||||||
CommandParameter = "https://github.com/BlossomiShymae/Needlework.Net/releases",
|
|
||||||
Content = "Download"
|
|
||||||
}));
|
|
||||||
IsUpdateShown = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FetchDataAsync()
|
|
||||||
{
|
|
||||||
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
|
||||||
HostDocument = document;
|
|
||||||
var handler = new OpenApiDocumentWrapper(document);
|
|
||||||
OpenApiDocumentWrapper = handler;
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
|
||||||
IsBusy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(DataRequestMessage message)
|
|
||||||
{
|
|
||||||
message.Reply(OpenApiDocumentWrapper!);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(HostDocumentRequestMessage message)
|
|
||||||
{
|
|
||||||
message.Reply(HostDocument!);
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void OpenUrl(string url)
|
|
||||||
{
|
|
||||||
var process = new Process()
|
|
||||||
{
|
|
||||||
StartInfo = new ProcessStartInfo(url)
|
|
||||||
{
|
|
||||||
UseShellExecute = true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(InfoBarUpdateMessage message)
|
|
||||||
{
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(async () => await ShowInfoBarAsync(message.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ShowInfoBarAsync(InfoBarViewModel vm)
|
|
||||||
{
|
|
||||||
InfoBarItems.Add(vm);
|
|
||||||
await Task.Delay(vm.Duration);
|
|
||||||
InfoBarItems.Remove(vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(OopsiesDialogRequestedMessage message)
|
|
||||||
{
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () => await DialogService.ShowAsync<OopsiesDialog>(message.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Needlework.Net.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 string? RequestTemplate { 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);
|
|
||||||
RequestTemplate = GetRequestTemplate(operation.RequestBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? GetRequestTemplate(OpenApiRequestBody? requestBody)
|
|
||||||
{
|
|
||||||
var requestClasses = GetRequestClasses(requestBody);
|
|
||||||
if (requestClasses.Count == 0)
|
|
||||||
{
|
|
||||||
var type = GetRequestBodyType(requestBody);
|
|
||||||
if (type == null) return null;
|
|
||||||
return GetRequestDefaultValue(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
var template = CreateTemplate(requestClasses);
|
|
||||||
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> CreateTemplate(AvaloniaList<PropertyClassViewModel> requestClasses)
|
|
||||||
{
|
|
||||||
if (requestClasses.Count == 0) return [];
|
|
||||||
List<string> template = [];
|
|
||||||
template.Add("{");
|
|
||||||
|
|
||||||
var rootClass = requestClasses.First();
|
|
||||||
if (rootClass.PropertyEnums.Any()) return [rootClass.PropertyEnums.First().Values];
|
|
||||||
var propertyFields = rootClass.PropertyFields;
|
|
||||||
for (int i = 0; i < propertyFields.Count; i++)
|
|
||||||
{
|
|
||||||
template.Add($"\"{propertyFields[i].Name}\"");
|
|
||||||
template.Add(":");
|
|
||||||
template.Add($"#{propertyFields[i].Type}");
|
|
||||||
|
|
||||||
if (i == propertyFields.Count - 1) template.Add("}");
|
|
||||||
else template.Add(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < template.Count; i++)
|
|
||||||
{
|
|
||||||
var type = template[i];
|
|
||||||
if (!type.Contains("#")) continue;
|
|
||||||
|
|
||||||
var foundClass = requestClasses.Where(c => c.Id == type.Replace("#", string.Empty));
|
|
||||||
if (foundClass.Any())
|
|
||||||
{
|
|
||||||
if (foundClass.First().PropertyEnums.Any())
|
|
||||||
{
|
|
||||||
template[i] = string.Join(string.Empty, CreateTemplate([.. foundClass]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AvaloniaList<PropertyClassViewModel> classes = [.. requestClasses];
|
|
||||||
classes.Remove(rootClass);
|
|
||||||
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
template[i] = GetRequestDefaultValue(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetRequestDefaultValue(string type)
|
|
||||||
{
|
|
||||||
var defaultValue = string.Empty;
|
|
||||||
if (type.Contains("[]")) defaultValue = "[]";
|
|
||||||
else if (type.Contains("string")) defaultValue = "\"\"";
|
|
||||||
else if (type.Contains("boolean")) defaultValue = "false";
|
|
||||||
else if (type.Contains("integer")) defaultValue = "0";
|
|
||||||
else if (type.Contains("double") || type.Contains("float")) defaultValue = "0.0";
|
|
||||||
else if (type.Contains("object")) defaultValue = "{}";
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
|
||||||
{
|
|
||||||
if (requestBody == null) return null;
|
|
||||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
|
||||||
{
|
|
||||||
var schema = media.Schema;
|
|
||||||
if (schema == null) return null; // Because "PostLolAccountVerificationV1SendDeactivationPin" exists where the media body is empty...
|
|
||||||
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) continue;
|
|
||||||
pathParameters.Add(new ParameterViewModel(parameter.Name, GetSchemaType(parameter.Schema), 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);
|
|
||||||
|
|
||||||
if (propertyClasses.Where(c => c.Id == componentId).Any()) return; // Avoid adding duplicate schemas in classes
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
|
|
||||||
public abstract partial class PageBase(string displayName, string icon, int index = 0) : ObservableValidator
|
|
||||||
{
|
|
||||||
[ObservableProperty] private string _displayName = displayName;
|
|
||||||
[ObservableProperty] private string _icon = icon;
|
|
||||||
[ObservableProperty] private int _index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
Needlework.Net/ViewModels/Pages/AboutViewModel.cs
Normal file
25
Needlework.Net/ViewModels/Pages/AboutViewModel.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
|
public partial class AboutViewModel : PageBase
|
||||||
|
{
|
||||||
|
public HttpClient HttpClient { get; }
|
||||||
|
|
||||||
|
public AboutViewModel(HttpClient httpClient) : base("About", "info-circle")
|
||||||
|
{
|
||||||
|
HttpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenUrl(string url)
|
||||||
|
{
|
||||||
|
var process = new Process()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
98
Needlework.Net/ViewModels/Pages/ConsoleViewModel.cs
Normal file
98
Needlework.Net/ViewModels/Pages/ConsoleViewModel.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
|
public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
|
||||||
|
{
|
||||||
|
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
||||||
|
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
[ObservableProperty] private bool _isRequestBusy = false;
|
||||||
|
[ObservableProperty] private string? _requestMethodSelected = "GET";
|
||||||
|
[ObservableProperty] private string? _requestPath = null;
|
||||||
|
[ObservableProperty] private string? _requestBody = null;
|
||||||
|
[ObservableProperty] private string? _responsePath = null;
|
||||||
|
[ObservableProperty] private string? _responseStatus = null;
|
||||||
|
[ObservableProperty] private string? _responseAuthorization = null;
|
||||||
|
|
||||||
|
public ConsoleViewModel() : base("Console", "terminal", -200)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task SendRequest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsRequestBusy = true;
|
||||||
|
if (string.IsNullOrEmpty(RequestPath)) throw new Exception("Path is empty.");
|
||||||
|
|
||||||
|
var method = RequestMethodSelected 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."),
|
||||||
|
};
|
||||||
|
|
||||||
|
var processInfo = ProcessFinder.Get();
|
||||||
|
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "ConsoleRequestEditor").Response;
|
||||||
|
var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||||
|
var client = Connector.GetLcuHttpClientInstance();
|
||||||
|
var response = await client.SendAsync(new(method, RequestPath) { Content = content });
|
||||||
|
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body));
|
||||||
|
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
|
||||||
|
}
|
||||||
|
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(body), nameof(ConsoleViewModel));
|
||||||
|
|
||||||
|
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
|
||||||
|
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
|
||||||
|
ResponseAuthorization = $"Basic {riotAuthentication.Value}";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
||||||
|
ResponseStatus = null;
|
||||||
|
ResponsePath = null;
|
||||||
|
ResponseAuthorization = null;
|
||||||
|
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsRequestBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(DataReadyMessage message)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||||
|
{
|
||||||
|
RequestPaths.Clear();
|
||||||
|
RequestPaths.AddRange(message.Value.Paths);
|
||||||
|
IsBusy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public string Endpoint { get; }
|
||||||
|
public string Title => Endpoint;
|
||||||
|
|
||||||
|
|
||||||
|
public IAvaloniaReadOnlyList<PathOperationViewModel> PathOperations { get; }
|
||||||
|
[ObservableProperty] private PathOperationViewModel? _selectedPathOperation;
|
||||||
|
|
||||||
|
[ObservableProperty] private string? _search;
|
||||||
|
public IAvaloniaList<PathOperationViewModel> FilteredPathOperations { get; }
|
||||||
|
|
||||||
|
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)));
|
||||||
|
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSearchChanged(string? value)
|
||||||
|
{
|
||||||
|
FilteredPathOperations.Clear();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
FilteredPathOperations.AddRange(PathOperations);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FilteredPathOperations.AddRange(PathOperations.Where(o => o.Path.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedPathOperationChanged(PathOperationViewModel? value)
|
||||||
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(value.Operation.RequestTemplate ?? string.Empty, "EndpointRequestEditor")));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointsContainerViewModel : PageBase
|
||||||
|
{
|
||||||
|
[ObservableProperty] private ObservableObject _activeViewModel;
|
||||||
|
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
||||||
|
[ObservableProperty] private string _title = string.Empty;
|
||||||
|
|
||||||
|
public EndpointsContainerViewModel(HttpClient httpClient) : base("Endpoints", "list-alt", -500)
|
||||||
|
{
|
||||||
|
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(httpClient, OnClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClicked(ObservableObject viewModel)
|
||||||
|
{
|
||||||
|
ActiveViewModel = viewModel;
|
||||||
|
if (viewModel is EndpointViewModel endpoint) Title = endpoint.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void GoBack()
|
||||||
|
{
|
||||||
|
ActiveViewModel = EndpointsViewModel;
|
||||||
|
Title = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointsViewModel : ObservableObject, IRecipient<DataReadyMessage>
|
||||||
|
{
|
||||||
|
public HttpClient HttpClient { get; }
|
||||||
|
|
||||||
|
public string Title => "Endpoints";
|
||||||
|
public Action<ObservableObject> OnClicked;
|
||||||
|
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
|
||||||
|
public IAvaloniaList<string> Query { get; } = new AvaloniaList<string>();
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
[ObservableProperty] private string _search = string.Empty;
|
||||||
|
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
||||||
|
|
||||||
|
public EndpointsViewModel(HttpClient httpClient, Action<ObservableObject> onClicked)
|
||||||
|
{
|
||||||
|
HttpClient = httpClient;
|
||||||
|
OnClicked = onClicked;
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(DataReadyMessage message)
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
Plugins.Clear();
|
||||||
|
Plugins.AddRange(message.Value.Plugins.Keys);
|
||||||
|
Query.Clear();
|
||||||
|
Query.AddRange(Plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSearchChanged(string value)
|
||||||
|
{
|
||||||
|
Query.Clear();
|
||||||
|
if (!string.IsNullOrEmpty(Search))
|
||||||
|
Query.AddRange(Plugins.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
||||||
|
else
|
||||||
|
Query.AddRange(Plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenEndpoint(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value)) return;
|
||||||
|
|
||||||
|
OnClicked.Invoke(new EndpointViewModel(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
232
Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs
Normal file
232
Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
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 string? RequestTemplate { 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);
|
||||||
|
RequestTemplate = GetRequestTemplate(operation.RequestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetRequestTemplate(OpenApiRequestBody? requestBody)
|
||||||
|
{
|
||||||
|
var requestClasses = GetRequestClasses(requestBody);
|
||||||
|
if (requestClasses.Count == 0)
|
||||||
|
{
|
||||||
|
var type = GetRequestBodyType(requestBody);
|
||||||
|
if (type == null) return null;
|
||||||
|
return GetRequestDefaultValue(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
var template = CreateTemplate(requestClasses);
|
||||||
|
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> CreateTemplate(AvaloniaList<PropertyClassViewModel> requestClasses)
|
||||||
|
{
|
||||||
|
if (requestClasses.Count == 0) return [];
|
||||||
|
List<string> template = [];
|
||||||
|
template.Add("{");
|
||||||
|
|
||||||
|
var rootClass = requestClasses.First();
|
||||||
|
if (rootClass.PropertyEnums.Any()) return [rootClass.PropertyEnums.First().Values];
|
||||||
|
var propertyFields = rootClass.PropertyFields;
|
||||||
|
for (int i = 0; i < propertyFields.Count; i++)
|
||||||
|
{
|
||||||
|
template.Add($"\"{propertyFields[i].Name}\"");
|
||||||
|
template.Add(":");
|
||||||
|
template.Add($"#{propertyFields[i].Type}");
|
||||||
|
|
||||||
|
if (i == propertyFields.Count - 1) template.Add("}");
|
||||||
|
else template.Add(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < template.Count; i++)
|
||||||
|
{
|
||||||
|
var type = template[i];
|
||||||
|
if (!type.Contains("#")) continue;
|
||||||
|
|
||||||
|
var foundClass = requestClasses.Where(c => c.Id == type.Replace("#", string.Empty));
|
||||||
|
if (foundClass.Any())
|
||||||
|
{
|
||||||
|
if (foundClass.First().PropertyEnums.Any())
|
||||||
|
{
|
||||||
|
template[i] = string.Join(string.Empty, CreateTemplate([.. foundClass]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AvaloniaList<PropertyClassViewModel> classes = [.. requestClasses];
|
||||||
|
classes.Remove(rootClass);
|
||||||
|
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
template[i] = GetRequestDefaultValue(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRequestDefaultValue(string type)
|
||||||
|
{
|
||||||
|
var defaultValue = string.Empty;
|
||||||
|
if (type.Contains("[]")) defaultValue = "[]";
|
||||||
|
else if (type.Contains("string")) defaultValue = "\"\"";
|
||||||
|
else if (type.Contains("boolean")) defaultValue = "false";
|
||||||
|
else if (type.Contains("integer")) defaultValue = "0";
|
||||||
|
else if (type.Contains("double") || type.Contains("float")) defaultValue = "0.0";
|
||||||
|
else if (type.Contains("object")) defaultValue = "{}";
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
||||||
|
{
|
||||||
|
if (requestBody == null) return null;
|
||||||
|
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var schema = media.Schema;
|
||||||
|
if (schema == null) return null; // Because "PostLolAccountVerificationV1SendDeactivationPin" exists where the media body is empty...
|
||||||
|
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) continue;
|
||||||
|
pathParameters.Add(new ParameterViewModel(parameter.Name, GetSchemaType(parameter.Schema), 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);
|
||||||
|
|
||||||
|
if (propertyClasses.Where(c => c.Id == componentId).Any()) return; // Avoid adding duplicate schemas in classes
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using Needlework.Net.Models;
|
||||||
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
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 Lazy<ResponseViewModel> _response;
|
||||||
|
|
||||||
|
public PathOperationViewModel(PathOperation pathOperation)
|
||||||
|
{
|
||||||
|
Method = pathOperation.Method.ToUpper();
|
||||||
|
Color = new SolidColorBrush(GetColor(Method));
|
||||||
|
Path = pathOperation.Path;
|
||||||
|
Operation = new OperationViewModel(pathOperation.Operation);
|
||||||
|
Response = new(() => new ResponseViewModel(pathOperation.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task SendRequest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
|
||||||
|
var method = 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 missing.")
|
||||||
|
};
|
||||||
|
|
||||||
|
var processInfo = ProcessFinder.Get();
|
||||||
|
var sb = new StringBuilder(Path);
|
||||||
|
foreach (var pathParameter in Operation.PathParameters)
|
||||||
|
{
|
||||||
|
sb.Replace($"{{{pathParameter.Name}}}", pathParameter.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstQueryAdded = false;
|
||||||
|
foreach (var queryParameter in Operation.QueryParameters)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(queryParameter.Value))
|
||||||
|
{
|
||||||
|
sb.Append(firstQueryAdded ? '&' : '?');
|
||||||
|
firstQueryAdded = true;
|
||||||
|
sb.Append($"{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var uri = sb.ToString();
|
||||||
|
|
||||||
|
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "EndpointRequestEditor").Response;
|
||||||
|
var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||||
|
|
||||||
|
var client = Connector.GetLcuHttpClientInstance();
|
||||||
|
var response = await client.SendAsync(new(method, uri) { Content = content });
|
||||||
|
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||||
|
var responseBytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
|
||||||
|
var responseBody = responseBytes.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBytes), App.JsonSerializerOptions) : string.Empty;
|
||||||
|
if (responseBody.Length >= App.MaxCharacters)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(responseBody));
|
||||||
|
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(string.Empty, "EndpointResponseEditor")));
|
||||||
|
}
|
||||||
|
else WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(responseBody, "EndpointResponseEditor")));
|
||||||
|
|
||||||
|
Response.Value.Status = $"{(int)response.StatusCode} {response.StatusCode}";
|
||||||
|
Response.Value.Path = $"https://127.0.0.1:{processInfo.AppPort}{uri}";
|
||||||
|
Response.Value.Authentication = Response.Value.Authorization = $"Basic {riotAuthentication.Value}";
|
||||||
|
Response.Value.Username = riotAuthentication.Username;
|
||||||
|
Response.Value.Password = riotAuthentication.Password;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
||||||
|
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.")
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.OpenApi.Any;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
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())}]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public class PropertyFieldViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Type { get; }
|
||||||
|
|
||||||
|
public PropertyFieldViewModel(string name, string type)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class ResponseViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty] private string? _path;
|
||||||
|
[ObservableProperty] private string? _status;
|
||||||
|
[ObservableProperty] private string? _authentication;
|
||||||
|
[ObservableProperty] private string? _username;
|
||||||
|
[ObservableProperty] private string? _password;
|
||||||
|
[ObservableProperty] private string? _authorization;
|
||||||
|
|
||||||
|
public ResponseViewModel(string path)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
var processInfo = GetProcessInfo();
|
||||||
|
if (processInfo != null)
|
||||||
|
{
|
||||||
|
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||||
|
Path = $"https://127.0.0.1:{processInfo.AppPort}{path}";
|
||||||
|
Username = riotAuthentication.Username;
|
||||||
|
Password = riotAuthentication.Password;
|
||||||
|
Authorization = $"Basic {riotAuthentication.RawValue}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProcessInfo? GetProcessInfo()
|
||||||
|
{
|
||||||
|
if (ProcessFinder.IsActive()) return ProcessFinder.Get();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Needlework.Net/ViewModels/Pages/HomeViewModel.cs
Normal file
19
Needlework.Net/ViewModels/Pages/HomeViewModel.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
|
public partial class HomeViewModel : PageBase
|
||||||
|
{
|
||||||
|
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenUrl(string url)
|
||||||
|
{
|
||||||
|
var process = new Process()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Needlework.Net/ViewModels/Pages/PageBase.cs
Normal file
11
Needlework.Net/ViewModels/Pages/PageBase.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract partial class PageBase(string displayName, string icon, int index = 0) : ObservableValidator
|
||||||
|
{
|
||||||
|
[ObservableProperty] private string _displayName = displayName;
|
||||||
|
[ObservableProperty] private string _icon = icon;
|
||||||
|
[ObservableProperty] private int _index = index;
|
||||||
|
}
|
||||||
21
Needlework.Net/ViewModels/Pages/Websocket/EventViewModel.cs
Normal file
21
Needlework.Net/ViewModels/Pages/Websocket/EventViewModel.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Websocket;
|
||||||
|
|
||||||
|
public class EventViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public string Time { get; }
|
||||||
|
public string Type { get; }
|
||||||
|
public string Uri { get; }
|
||||||
|
|
||||||
|
public string Key => $"{Time} {Type} {Uri}";
|
||||||
|
|
||||||
|
public EventViewModel(EventData eventData)
|
||||||
|
{
|
||||||
|
Time = $"{DateTime.Now:HH:mm:ss.fff}";
|
||||||
|
Type = eventData?.EventType.ToUpper() ?? string.Empty;
|
||||||
|
Uri = eventData?.Uri ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
125
Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs
Normal file
125
Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using Websocket.Client;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Websocket;
|
||||||
|
|
||||||
|
public partial class WebsocketViewModel : PageBase
|
||||||
|
{
|
||||||
|
public ObservableCollection<EventViewModel> EventLog { get; } = [];
|
||||||
|
public SemaphoreSlim EventLogLock { get; } = new(1, 1);
|
||||||
|
|
||||||
|
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
||||||
|
[ObservableProperty] private string _search = string.Empty;
|
||||||
|
[ObservableProperty] private bool _isAttach = true;
|
||||||
|
[ObservableProperty] private bool _isTail = false;
|
||||||
|
[ObservableProperty] private EventViewModel? _selectedEventLog = null;
|
||||||
|
|
||||||
|
private Dictionary<string, EventMessage> _events = [];
|
||||||
|
|
||||||
|
public WebsocketClient? Client { get; set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
||||||
|
|
||||||
|
public WebsocketViewModel() : base("Event Viewer", "plug", -100)
|
||||||
|
{
|
||||||
|
EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
||||||
|
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeWebsocket()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
Client = client;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception) { }
|
||||||
|
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedEventLogChanged(EventViewModel? value)
|
||||||
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
if (_events.TryGetValue(value.Key, out var message))
|
||||||
|
{
|
||||||
|
var text = JsonSerializer.Serialize(message, App.JsonSerializerOptions);
|
||||||
|
if (text.Length >= App.MaxCharacters) WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(text));
|
||||||
|
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(text), nameof(WebsocketViewModel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Clear()
|
||||||
|
{
|
||||||
|
_events.Clear();
|
||||||
|
EventLog.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnReconnection(ReconnectionInfo info)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"-- Reconnection --\nType{info.Type}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisconnection(DisconnectionInfo info)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"-- Disconnection --\nType:{info.Type}\nSubProocol:{info.SubProtocol}\nCloseStatus:{info.CloseStatus}\nCloseStatusDescription:{info.CloseStatusDescription}\nExceptionMessage:{info?.Exception?.Message}\n:InnerException:{info?.Exception?.InnerException}");
|
||||||
|
Client?.Dispose();
|
||||||
|
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMessage(EventMessage message)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () =>
|
||||||
|
{
|
||||||
|
if (!IsAttach) return;
|
||||||
|
|
||||||
|
var line = new EventViewModel(message.Data!);
|
||||||
|
|
||||||
|
await EventLogLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (EventLog.Count < 1000)
|
||||||
|
{
|
||||||
|
EventLog.Add(line);
|
||||||
|
_events[line.Key] = message;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var _event = EventLog[0];
|
||||||
|
EventLog.RemoveAt(0);
|
||||||
|
_events.Remove(_event.Key);
|
||||||
|
|
||||||
|
EventLog.Add(line);
|
||||||
|
_events[line.Key] = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EventLogLock.Release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
|
|
||||||
namespace Needlework.Net.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
using Avalonia.Media;
|
|
||||||
using BlossomiShymae.GrrrLCU;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using Needlework.Net.Models;
|
|
||||||
using System;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Needlework.Net.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 Lazy<ResponseViewModel> _response;
|
|
||||||
|
|
||||||
public PathOperationViewModel(PathOperation pathOperation)
|
|
||||||
{
|
|
||||||
Method = pathOperation.Method.ToUpper();
|
|
||||||
Color = new SolidColorBrush(GetColor(Method));
|
|
||||||
Path = pathOperation.Path;
|
|
||||||
Operation = new OperationViewModel(pathOperation.Operation);
|
|
||||||
Response = new(() => new ResponseViewModel(pathOperation.Path));
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
public async Task SendRequest()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IsBusy = true;
|
|
||||||
|
|
||||||
var method = 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 missing.")
|
|
||||||
};
|
|
||||||
|
|
||||||
var processInfo = ProcessFinder.Get();
|
|
||||||
var sb = new StringBuilder(Path);
|
|
||||||
foreach (var pathParameter in Operation.PathParameters)
|
|
||||||
{
|
|
||||||
sb.Replace($"{{{pathParameter.Name}}}", pathParameter.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstQueryAdded = false;
|
|
||||||
foreach (var queryParameter in Operation.QueryParameters)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(queryParameter.Value))
|
|
||||||
{
|
|
||||||
sb.Append(firstQueryAdded ? '&' : '?');
|
|
||||||
firstQueryAdded = true;
|
|
||||||
sb.Append($"{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var uri = sb.ToString();
|
|
||||||
|
|
||||||
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "EndpointRequestEditor").Response;
|
|
||||||
var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
|
||||||
|
|
||||||
var client = Connector.GetLcuHttpClientInstance();
|
|
||||||
var response = await client.SendAsync(new(method, uri) { Content = content });
|
|
||||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
|
||||||
var responseBytes = await response.Content.ReadAsByteArrayAsync();
|
|
||||||
|
|
||||||
var responseBody = responseBytes.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBytes), App.JsonSerializerOptions) : string.Empty;
|
|
||||||
if (responseBody.Length >= App.MaxCharacters)
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(responseBody));
|
|
||||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(string.Empty, "EndpointResponseEditor")));
|
|
||||||
}
|
|
||||||
else WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(responseBody, "EndpointResponseEditor")));
|
|
||||||
|
|
||||||
Response.Value.Status = $"{(int)response.StatusCode} {response.StatusCode}";
|
|
||||||
Response.Value.Path = $"https://127.0.0.1:{processInfo.AppPort}{uri}";
|
|
||||||
Response.Value.Authentication = Response.Value.Authorization = $"Basic {riotAuthentication.Value}";
|
|
||||||
Response.Value.Username = riotAuthentication.Username;
|
|
||||||
Response.Value.Password = riotAuthentication.Password;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
|
||||||
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.")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Microsoft.OpenApi.Any;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Needlework.Net.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())}]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public class PropertyFieldViewModel
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public string Type { get; }
|
|
||||||
|
|
||||||
public PropertyFieldViewModel(string name, string type)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using BlossomiShymae.GrrrLCU;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class ResponseViewModel : ObservableObject
|
|
||||||
{
|
|
||||||
[ObservableProperty] private string? _path;
|
|
||||||
[ObservableProperty] private string? _status;
|
|
||||||
[ObservableProperty] private string? _authentication;
|
|
||||||
[ObservableProperty] private string? _username;
|
|
||||||
[ObservableProperty] private string? _password;
|
|
||||||
[ObservableProperty] private string? _authorization;
|
|
||||||
|
|
||||||
public ResponseViewModel(string path)
|
|
||||||
{
|
|
||||||
Path = path;
|
|
||||||
var processInfo = GetProcessInfo();
|
|
||||||
if (processInfo != null)
|
|
||||||
{
|
|
||||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
|
||||||
Path = $"https://127.0.0.1:{processInfo.AppPort}{path}";
|
|
||||||
Username = riotAuthentication.Username;
|
|
||||||
Password = riotAuthentication.Password;
|
|
||||||
Authorization = $"Basic {riotAuthentication.RawValue}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ProcessInfo? GetProcessInfo()
|
|
||||||
{
|
|
||||||
if (ProcessFinder.IsActive()) return ProcessFinder.Get();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
using BlossomiShymae.GrrrLCU;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using Websocket.Client;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class WebsocketViewModel : PageBase
|
|
||||||
{
|
|
||||||
public ObservableCollection<EventViewModel> EventLog { get; } = [];
|
|
||||||
public SemaphoreSlim EventLogLock { get; } = new(1, 1);
|
|
||||||
|
|
||||||
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
|
||||||
[ObservableProperty] private string _search = string.Empty;
|
|
||||||
[ObservableProperty] private bool _isAttach = true;
|
|
||||||
[ObservableProperty] private bool _isTail = false;
|
|
||||||
[ObservableProperty] private EventViewModel? _selectedEventLog = null;
|
|
||||||
|
|
||||||
private Dictionary<string, EventMessage> _events = [];
|
|
||||||
|
|
||||||
public WebsocketClient? Client { get; set; }
|
|
||||||
|
|
||||||
public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
|
||||||
|
|
||||||
public WebsocketViewModel() : base("Event Viewer", "plug", -100)
|
|
||||||
{
|
|
||||||
EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
|
||||||
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
|
||||||
thread.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeWebsocket()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
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));
|
|
||||||
Client = client;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
Thread.Sleep(TimeSpan.FromSeconds(5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnSelectedEventLogChanged(EventViewModel? value)
|
|
||||||
{
|
|
||||||
if (value == null) return;
|
|
||||||
if (_events.TryGetValue(value.Key, out var message))
|
|
||||||
{
|
|
||||||
var text = JsonSerializer.Serialize(message, App.JsonSerializerOptions);
|
|
||||||
if (text.Length >= App.MaxCharacters) WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(text));
|
|
||||||
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(text), nameof(WebsocketViewModel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void Clear()
|
|
||||||
{
|
|
||||||
_events.Clear();
|
|
||||||
EventLog.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnReconnection(ReconnectionInfo info)
|
|
||||||
{
|
|
||||||
Trace.WriteLine($"-- Reconnection --\nType{info.Type}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDisconnection(DisconnectionInfo info)
|
|
||||||
{
|
|
||||||
Trace.WriteLine($"-- Disconnection --\nType:{info.Type}\nSubProocol:{info.SubProtocol}\nCloseStatus:{info.CloseStatus}\nCloseStatusDescription:{info.CloseStatusDescription}\nExceptionMessage:{info?.Exception?.Message}\n:InnerException:{info?.Exception?.InnerException}");
|
|
||||||
Client?.Dispose();
|
|
||||||
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
|
||||||
thread.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMessage(EventMessage message)
|
|
||||||
{
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () =>
|
|
||||||
{
|
|
||||||
if (!IsAttach) return;
|
|
||||||
|
|
||||||
var line = new EventViewModel(message.Data!);
|
|
||||||
|
|
||||||
await EventLogLock.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (EventLog.Count < 1000)
|
|
||||||
{
|
|
||||||
EventLog.Add(line);
|
|
||||||
_events[line.Key] = message;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var _event = EventLog[0];
|
|
||||||
EventLog.RemoveAt(0);
|
|
||||||
_events.Remove(_event.Key);
|
|
||||||
|
|
||||||
EventLog.Add(line);
|
|
||||||
_events[line.Key] = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
EventLogLock.Release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Views
|
|
||||||
{
|
|
||||||
public partial class EndpointsView : UserControl
|
|
||||||
{
|
|
||||||
public EndpointsView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Views
|
|
||||||
{
|
|
||||||
public partial class HomeView : UserControl
|
|
||||||
{
|
|
||||||
public HomeView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.MainWindow"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.MainWindow"
|
x:Class="Needlework.Net.Views.MainWindow.MainWindowView"
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Title="Needlework.Net"
|
Title="Needlework.Net"
|
||||||
Icon="/Assets/app.ico"
|
Icon="/Assets/app.ico"
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
using FluentAvalonia.UI.Windowing;
|
using FluentAvalonia.UI.Windowing;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.MainWindow;
|
||||||
|
|
||||||
public partial class MainWindow : AppWindow
|
public partial class MainWindowView : AppWindow
|
||||||
{
|
{
|
||||||
public MainWindow()
|
public MainWindowView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
64
Needlework.Net/Views/MainWindow/OopsiesDialog.cs
Normal file
64
Needlework.Net/Views/MainWindow/OopsiesDialog.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Needlework.Net.Services;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.MainWindow;
|
||||||
|
|
||||||
|
public class OopsiesDialog : IDialog, IDisposable
|
||||||
|
{
|
||||||
|
private bool _isDisposing;
|
||||||
|
private string? _text;
|
||||||
|
private ContentDialog _dialog;
|
||||||
|
|
||||||
|
public OopsiesDialog()
|
||||||
|
{
|
||||||
|
_dialog = new ContentDialog
|
||||||
|
{
|
||||||
|
PrimaryButtonText = "Open",
|
||||||
|
CloseButtonText = "Cancel",
|
||||||
|
Title = "Oopsies",
|
||||||
|
Content = "This response is too large to handle for performance reasons.\nIt can be viewed in an external editor or viewer.",
|
||||||
|
IsPrimaryButtonEnabled = true,
|
||||||
|
IsSecondaryButtonEnabled = false,
|
||||||
|
DefaultButton = ContentDialogButton.Primary
|
||||||
|
};
|
||||||
|
_dialog.PrimaryButtonClick += OnPrimaryButtonClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ContentDialogResult> ShowAsync(object data)
|
||||||
|
{
|
||||||
|
_text = (string)data;
|
||||||
|
var result = await _dialog.ShowAsync();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
|
{
|
||||||
|
var temp = Path.GetTempFileName().Replace(".tmp", ".json");
|
||||||
|
File.WriteAllText(temp, _text);
|
||||||
|
Process.Start("explorer", "\"" + temp + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_text = null;
|
||||||
|
_dialog.PrimaryButtonClick -= OnPrimaryButtonClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using FluentAvalonia.UI.Controls;
|
|
||||||
using Needlework.Net.Services;
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Views
|
|
||||||
{
|
|
||||||
public class OopsiesDialog : IDialog, IDisposable
|
|
||||||
{
|
|
||||||
private bool _isDisposing;
|
|
||||||
private string? _text;
|
|
||||||
private ContentDialog _dialog;
|
|
||||||
|
|
||||||
public OopsiesDialog()
|
|
||||||
{
|
|
||||||
_dialog = new ContentDialog
|
|
||||||
{
|
|
||||||
PrimaryButtonText = "Open",
|
|
||||||
CloseButtonText = "Cancel",
|
|
||||||
Title = "Oopsies",
|
|
||||||
Content = "This response is too large to handle for performance reasons.\nIt can be viewed in an external editor or viewer.",
|
|
||||||
IsPrimaryButtonEnabled = true,
|
|
||||||
IsSecondaryButtonEnabled = false,
|
|
||||||
DefaultButton = ContentDialogButton.Primary
|
|
||||||
};
|
|
||||||
_dialog.PrimaryButtonClick += OnPrimaryButtonClick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ContentDialogResult> ShowAsync(object data)
|
|
||||||
{
|
|
||||||
_text = (string)data;
|
|
||||||
var result = await _dialog.ShowAsync();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
|
||||||
{
|
|
||||||
var temp = Path.GetTempFileName().Replace(".tmp", ".json");
|
|
||||||
File.WriteAllText(temp, _text);
|
|
||||||
Process.Start("explorer", "\"" + temp + "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!_isDisposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_text = null;
|
|
||||||
_dialog.PrimaryButtonClick -= OnPrimaryButtonClick;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isDisposing = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
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:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.AboutView"
|
x:Class="Needlework.Net.Views.Pages.AboutView"
|
||||||
x:DataType="vm:AboutViewModel">
|
x:DataType="vm:AboutViewModel">
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Style Selector="Button">
|
<Style Selector="Button">
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
public partial class AboutView : UserControl
|
public partial class AboutView : UserControl
|
||||||
{
|
{
|
||||||
@@ -5,10 +5,10 @@
|
|||||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.ConsoleView"
|
x:Class="Needlework.Net.Views.Pages.ConsoleView"
|
||||||
x:DataType="vm:ConsoleViewModel">
|
x:DataType="vm:ConsoleViewModel">
|
||||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||||
BusyText="Loading...">
|
BusyText="Loading...">
|
||||||
@@ -5,10 +5,10 @@ using AvaloniaEdit;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.Pages;
|
||||||
using TextMateSharp.Grammars;
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessage>, IRecipient<ContentRequestMessage>
|
public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessage>, IRecipient<ContentRequestMessage>
|
||||||
{
|
{
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
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:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.EndpointView"
|
x:Class="Needlework.Net.Views.Pages.EndpointView"
|
||||||
x:DataType="vm:EndpointViewModel">
|
x:DataType="vm:EndpointViewModel">
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Style Selector="DataGrid">
|
<Style Selector="DataGrid">
|
||||||
@@ -5,10 +5,10 @@ using AvaloniaEdit;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
using TextMateSharp.Grammars;
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>, IRecipient<ContentRequestMessage>
|
public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>, IRecipient<ContentRequestMessage>
|
||||||
{
|
{
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
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:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.EndpointsContainerView"
|
x:Class="Needlework.Net.Views.Pages.EndpointsContainerView"
|
||||||
x:DataType="vm:EndpointsContainerViewModel">
|
x:DataType="vm:EndpointsContainerViewModel">
|
||||||
<Grid RowDefinitions="auto,*"
|
<Grid RowDefinitions="auto,*"
|
||||||
ColumnDefinitions="*"
|
ColumnDefinitions="*"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
public partial class EndpointsContainerView : UserControl
|
public partial class EndpointsContainerView : UserControl
|
||||||
{
|
{
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
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:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
Name="EndpointsControl"
|
Name="EndpointsControl"
|
||||||
x:Class="Needlework.Net.Views.EndpointsView"
|
x:Class="Needlework.Net.Views.Pages.EndpointsView"
|
||||||
x:DataType="vm:EndpointsViewModel">
|
x:DataType="vm:EndpointsViewModel">
|
||||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||||
BusyText="Loading...">
|
BusyText="Loading...">
|
||||||
11
Needlework.Net/Views/Pages/EndpointsView.axaml.cs
Normal file
11
Needlework.Net/Views/Pages/EndpointsView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
|
public partial class EndpointsView : UserControl
|
||||||
|
{
|
||||||
|
public EndpointsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
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:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.HomeView"
|
x:Class="Needlework.Net.Views.Pages.HomeView"
|
||||||
x:DataType="vm:HomeViewModel">
|
x:DataType="vm:HomeViewModel">
|
||||||
<!-- TOP LEVEL -->
|
<!-- TOP LEVEL -->
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
11
Needlework.Net/Views/Pages/HomeView.axaml.cs
Normal file
11
Needlework.Net/Views/Pages/HomeView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
|
public partial class HomeView : UserControl
|
||||||
|
{
|
||||||
|
public HomeView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
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:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Websocket"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.WebsocketView"
|
x:Class="Needlework.Net.Views.Pages.WebsocketView"
|
||||||
x:DataType="vm:WebsocketViewModel">
|
x:DataType="vm:WebsocketViewModel">
|
||||||
<Grid RowDefinitions="*,auto,*" Margin="16">
|
<Grid RowDefinitions="*,auto,*" Margin="16">
|
||||||
<Border Grid.Row="0"
|
<Border Grid.Row="0"
|
||||||
@@ -6,11 +6,11 @@ using AvaloniaEdit;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.Pages.Websocket;
|
||||||
using System;
|
using System;
|
||||||
using TextMateSharp.Grammars;
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
public partial class WebsocketView : UserControl, IRecipient<ResponseUpdatedMessage>
|
public partial class WebsocketView : UserControl, IRecipient<ResponseUpdatedMessage>
|
||||||
{
|
{
|
||||||
Reference in New Issue
Block a user