mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 10:10:48 +01:00
Compare commits
13 Commits
375d5a2ff8
...
0.12.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adc8b0c0f1 | ||
|
|
be7d575b48 | ||
|
|
f9dd654b6a | ||
|
|
57d3eb4172 | ||
|
|
ce2336ab4d | ||
|
|
9a76e1af4a | ||
|
|
6f0126863b | ||
|
|
826134888e | ||
|
|
ef16642c04 | ||
|
|
a5f49c48b8 | ||
|
|
1364cdc38c | ||
|
|
c51f20a324 | ||
|
|
6d1acee8df |
@@ -15,6 +15,7 @@
|
|||||||
<sty:FluentAvaloniaTheme PreferSystemTheme="False" PreferUserAccentColor="False" />
|
<sty:FluentAvaloniaTheme PreferSystemTheme="False" PreferUserAccentColor="False" />
|
||||||
<materialIcons:MaterialIconStyles />
|
<materialIcons:MaterialIconStyles />
|
||||||
<StyleInclude Source="Controls/Card.axaml"/>
|
<StyleInclude Source="Controls/Card.axaml"/>
|
||||||
|
<StyleInclude Source="Controls/UserCard.axaml"/>
|
||||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ public partial class App(IServiceProvider serviceProvider) : Application
|
|||||||
MainWindow = desktop.MainWindow;
|
MainWindow = desktop.MainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIN
Needlework.Net/Assets/Users/aoshiw.png
Normal file
BIN
Needlework.Net/Assets/Users/aoshiw.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Needlework.Net/Assets/Users/sylv.jpg
Normal file
BIN
Needlework.Net/Assets/Users/sylv.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
71
Needlework.Net/Controls/UserCard.axaml
Normal file
71
Needlework.Net/Controls/UserCard.axaml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
|
xmlns:controls="using:Needlework.Net.Controls">
|
||||||
|
<Design.PreviewWith>
|
||||||
|
<WrapPanel>
|
||||||
|
<controls:UserCard
|
||||||
|
Width="300"
|
||||||
|
Height="400"
|
||||||
|
UserImage="/Assets/Users/blossomishymae.png"
|
||||||
|
UserName="estrogen elf"
|
||||||
|
UserGithub="BlossomiShymae">
|
||||||
|
Needlework.Net is the .NET rewrite of Needlework. This tool was made to help others with LCU and Game Client development. Feel free to ask any questions
|
||||||
|
or help contribute to the project! Made with love. 💜
|
||||||
|
</controls:UserCard>
|
||||||
|
</WrapPanel>
|
||||||
|
</Design.PreviewWith>
|
||||||
|
<Style Selector="controls|UserCard">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<Grid>
|
||||||
|
<Border CornerRadius="16,16,16,16"
|
||||||
|
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
Margin="0 50 0 0"
|
||||||
|
Padding="16 66 16 16">
|
||||||
|
<Grid RowDefinitions="auto,auto,auto"
|
||||||
|
ColumnDefinitions="*">
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||||
|
Margin="0 0 0 4"
|
||||||
|
Text="{TemplateBinding UserName}"/>
|
||||||
|
<Grid Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
RowDefinitions="*"
|
||||||
|
ColumnDefinitions="auto,auto"
|
||||||
|
Margin="0 0 0 16">
|
||||||
|
<Button Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Theme="{StaticResource TransparentButton}"
|
||||||
|
FontSize="20"
|
||||||
|
Name="PART_GithubButton">
|
||||||
|
<i:Icon Value="fa-github"/>
|
||||||
|
</Button>
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="8 0 0 0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{TemplateBinding UserGithub}"/>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
TextWrapping="WrapWithOverflow"
|
||||||
|
Text="{TemplateBinding Content}"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<Border CornerRadius="100"
|
||||||
|
Width="100"
|
||||||
|
Height="100"
|
||||||
|
Margin="{TemplateBinding UserImageMargin}"
|
||||||
|
ClipToBounds="True">
|
||||||
|
<Image Source="{TemplateBinding UserImage}"
|
||||||
|
RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
91
Needlework.Net/Controls/UserCard.axaml.cs
Normal file
91
Needlework.Net/Controls/UserCard.axaml.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Metadata;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Controls;
|
||||||
|
|
||||||
|
[TemplatePart("PART_GithubButton", typeof(Button))]
|
||||||
|
public partial class UserCard : ContentControl
|
||||||
|
{
|
||||||
|
private Button? _githubButton;
|
||||||
|
|
||||||
|
public UserCard()
|
||||||
|
{
|
||||||
|
UserImageMargin = new(0, !double.IsNaN(Height) ? 100 - Height : 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IImage?> UserImageProperty =
|
||||||
|
AvaloniaProperty.Register<UserCard, IImage?>(nameof(UserImage), defaultValue: null);
|
||||||
|
|
||||||
|
public IImage? UserImage
|
||||||
|
{
|
||||||
|
get { return GetValue(UserImageProperty); }
|
||||||
|
set { SetValue(UserImageProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string?> UserNameProperty =
|
||||||
|
AvaloniaProperty.Register<UserCard, string?>(nameof(UserName), defaultValue: null);
|
||||||
|
|
||||||
|
public string? UserName
|
||||||
|
{
|
||||||
|
get { return GetValue(UserNameProperty); }
|
||||||
|
set { SetValue(UserNameProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string?> UserGithubProperty =
|
||||||
|
AvaloniaProperty.Register<UserCard, string?>(nameof(UserGithub), defaultValue: null);
|
||||||
|
|
||||||
|
public string? UserGithub
|
||||||
|
{
|
||||||
|
get { return GetValue(UserGithubProperty); }
|
||||||
|
set { SetValue(UserGithubProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DirectProperty<UserCard, Thickness> UserImageMarginProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<UserCard, Thickness>(nameof(UserImageMargin), o => o.UserImageMargin);
|
||||||
|
|
||||||
|
private Thickness _userImageMargin = new(0, 0, 0, 0);
|
||||||
|
|
||||||
|
public Thickness UserImageMargin
|
||||||
|
{
|
||||||
|
get { return _userImageMargin; }
|
||||||
|
private set { SetAndRaise(UserImageMarginProperty, ref _userImageMargin, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
|
||||||
|
SizeChanged += UserCard_SizeChanged;
|
||||||
|
|
||||||
|
if (_githubButton != null)
|
||||||
|
{
|
||||||
|
_githubButton.Click -= GithubButton_Click;
|
||||||
|
}
|
||||||
|
|
||||||
|
_githubButton = e.NameScope.Find("PART_GithubButton") as Button;
|
||||||
|
|
||||||
|
if (_githubButton != null)
|
||||||
|
{
|
||||||
|
_githubButton.Click += GithubButton_Click;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserCard_SizeChanged(object? sender, SizeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
UserImageMargin = new(0, !double.IsNaN(e.NewSize.Height) ? 100 - e.NewSize.Height : 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GithubButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var process = new Process()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo($"https://github.com/{UserGithub}") { UseShellExecute = true }
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
60
Needlework.Net/DataSource.cs
Normal file
60
Needlework.Net/DataSource.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.OpenApi.Readers;
|
||||||
|
using Needlework.Net.Models;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net
|
||||||
|
{
|
||||||
|
public class DataSource
|
||||||
|
{
|
||||||
|
private readonly ILogger<DataSource> _logger;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private Document? _lcuSchemaDocument;
|
||||||
|
private Document? _lolClientDocument;
|
||||||
|
private readonly TaskCompletionSource<bool> _taskCompletionSource = new();
|
||||||
|
|
||||||
|
|
||||||
|
public DataSource(HttpClient httpClient, ILogger<DataSource> logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Document> GetLcuSchemaDocumentAsync()
|
||||||
|
{
|
||||||
|
await _taskCompletionSource.Task;
|
||||||
|
return _lcuSchemaDocument ?? throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Document> GetLolClientDocumentAsync()
|
||||||
|
{
|
||||||
|
await _taskCompletionSource.Task;
|
||||||
|
return _lolClientDocument ?? throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var reader = new OpenApiStreamReader();
|
||||||
|
var lcuSchemaStream = await _httpClient.GetStreamAsync("https://raw.githubusercontent.com/dysolix/hasagi-types/main/swagger.json");
|
||||||
|
var lcuSchemaRaw = reader.Read(lcuSchemaStream, out var _);
|
||||||
|
_lcuSchemaDocument = new Document(lcuSchemaRaw);
|
||||||
|
|
||||||
|
var lolClientStream = await _httpClient.GetStreamAsync("https://raw.githubusercontent.com/AlsoSylv/Irelia/refs/heads/master/schemas/game_schema.json");
|
||||||
|
var lolClientRaw = reader.Read(lolClientStream, out var _);
|
||||||
|
_lolClientDocument = new Document(lolClientRaw);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to initialize DataSource");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_taskCompletionSource.SetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Needlework.Net/Logger.cs
Normal file
27
Needlework.Net/Logger.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Needlework.Net
|
||||||
|
{
|
||||||
|
public static class Logger
|
||||||
|
{
|
||||||
|
public static void Setup(ILoggingBuilder builder)
|
||||||
|
{
|
||||||
|
var logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Debug()
|
||||||
|
.WriteTo.File("Logs/debug-", rollingInterval: RollingInterval.Day, shared: true)
|
||||||
|
.CreateLogger();
|
||||||
|
logger.Debug("NeedleworkDotNet version: {Version}", Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0");
|
||||||
|
logger.Debug("OS description: {Description}", System.Runtime.InteropServices.RuntimeInformation.OSDescription);
|
||||||
|
builder.AddSerilog(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogFatal(UnhandledExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
File.WriteAllText($"Logs/fatal-{DateTime.Now:HHmmssfff}", e.ExceptionObject.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
|
||||||
using Needlework.Net.Models;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Messages
|
|
||||||
{
|
|
||||||
public class DataReadyMessage(OpenApiDocumentWrapper wrapper) : ValueChangedMessage<OpenApiDocumentWrapper>(wrapper)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
|
||||||
using Needlework.Net.Models;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Messages
|
|
||||||
{
|
|
||||||
public class DataRequestMessage : RequestMessage<OpenApiDocumentWrapper>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Messages
|
|
||||||
{
|
|
||||||
public class HostDocumentRequestMessage : RequestMessage<OpenApiDocument>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ using Microsoft.OpenApi.Models;
|
|||||||
|
|
||||||
namespace Needlework.Net.Models;
|
namespace Needlework.Net.Models;
|
||||||
|
|
||||||
public class OpenApiDocumentWrapper
|
public class Document
|
||||||
{
|
{
|
||||||
internal OpenApiDocument OpenApiDocument { get; }
|
internal OpenApiDocument OpenApiDocument { get; }
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ public class OpenApiDocumentWrapper
|
|||||||
|
|
||||||
public List<string> Paths => [.. OpenApiDocument.Paths.Keys];
|
public List<string> Paths => [.. OpenApiDocument.Paths.Keys];
|
||||||
|
|
||||||
public OpenApiDocumentWrapper(OpenApiDocument openApiDocument)
|
public Document(OpenApiDocument openApiDocument)
|
||||||
{
|
{
|
||||||
OpenApiDocument = openApiDocument;
|
OpenApiDocument = openApiDocument;
|
||||||
var plugins = new SortedDictionary<string, List<PathOperation>>();
|
var plugins = new SortedDictionary<string, List<PathOperation>>();
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Microsoft.OpenApi.Readers;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Models;
|
|
||||||
|
|
||||||
public static class Resources
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get the OpenApi document of the LCU schema. Provided by dysolix.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="httpClient"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static async Task<OpenApiDocument> GetOpenApiDocumentAsync(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
var stream = await httpClient.GetStreamAsync("https://raw.githubusercontent.com/dysolix/hasagi-types/main/swagger.json");
|
|
||||||
|
|
||||||
var document = new OpenApiStreamReader().Read(stream, out var _);
|
|
||||||
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
||||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||||
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
||||||
<AssemblyVersion>0.11.0.0</AssemblyVersion>
|
<AssemblyVersion>0.12.0.0</AssemblyVersion>
|
||||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||||
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -48,6 +48,13 @@
|
|||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AvaloniaXaml Remove="Utilities\**" />
|
||||||
|
<Compile Remove="Utilities\**" />
|
||||||
|
<EmbeddedResource Remove="Utilities\**" />
|
||||||
|
<None Remove="Utilities\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<UpToDateCheckInput Remove="Views\AboutView.axaml" />
|
<UpToDateCheckInput Remove="Views\AboutView.axaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -69,6 +76,5 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Assets\Users\" />
|
<Folder Include="Assets\Users\" />
|
||||||
<Folder Include="Utilities\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ using Needlework.Net.Extensions;
|
|||||||
using Needlework.Net.Services;
|
using Needlework.Net.Services;
|
||||||
using Needlework.Net.ViewModels.MainWindow;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
using Needlework.Net.ViewModels.Pages;
|
using Needlework.Net.ViewModels.Pages;
|
||||||
|
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
using Projektanker.Icons.Avalonia;
|
using Projektanker.Icons.Avalonia;
|
||||||
using Projektanker.Icons.Avalonia.FontAwesome;
|
using Projektanker.Icons.Avalonia.FontAwesome;
|
||||||
using Serilog;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net;
|
namespace Needlework.Net;
|
||||||
|
|
||||||
@@ -32,33 +32,43 @@ class Program
|
|||||||
{
|
{
|
||||||
IconProvider.Current
|
IconProvider.Current
|
||||||
.Register<FontAwesomeIconProvider>();
|
.Register<FontAwesomeIconProvider>();
|
||||||
|
var services = BuildServices();
|
||||||
|
Task.Run(async () => await InitializeDataSourceAsync(services));
|
||||||
|
|
||||||
return AppBuilder.Configure(() => new App(BuildServices()))
|
return AppBuilder.Configure(() => new App(services))
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.WithInterFont()
|
.WithInterFont()
|
||||||
.LogToTrace()
|
.LogToTrace()
|
||||||
.With(new Win32PlatformOptions
|
.With(new Win32PlatformOptions
|
||||||
{
|
{
|
||||||
CompositionMode = [ Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition ]
|
CompositionMode = [Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task InitializeDataSourceAsync(IServiceProvider services)
|
||||||
|
{
|
||||||
|
var dataSource = services.GetRequiredService<DataSource>();
|
||||||
|
await dataSource.InitializeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private static IServiceProvider BuildServices()
|
private static IServiceProvider BuildServices()
|
||||||
{
|
{
|
||||||
var builder = new ServiceCollection();
|
var builder = new ServiceCollection();
|
||||||
|
|
||||||
builder.AddSingleton<MainWindowViewModel>();
|
builder.AddSingleton<MainWindowViewModel>();
|
||||||
builder.AddSingleton<DialogService>();
|
builder.AddSingleton<DialogService>();
|
||||||
|
builder.AddSingleton<DataSource>();
|
||||||
builder.AddSingletonsFromAssemblies<PageBase>();
|
builder.AddSingletonsFromAssemblies<PageBase>();
|
||||||
builder.AddHttpClient();
|
builder.AddHttpClient();
|
||||||
|
builder.AddHttpClient(nameof(EndpointsTabViewModel)).ConfigurePrimaryHttpMessageHandler(() => // Insecure SSL for Game Client API
|
||||||
var logger = new LoggerConfiguration()
|
{
|
||||||
.MinimumLevel.Debug()
|
var handler = new HttpClientHandler
|
||||||
.WriteTo.File("Logs/NeedleworkDotNet.log", rollingInterval: RollingInterval.Day, shared: true)
|
{
|
||||||
.CreateLogger();
|
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
|
||||||
logger.Debug("NeedleworkDotNet version: {Version}", Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0");
|
};
|
||||||
logger.Debug("OS description: {Description}", System.Runtime.InteropServices.RuntimeInformation.OSDescription);
|
return handler;
|
||||||
builder.AddLogging(builder => builder.AddSerilog(logger));
|
});
|
||||||
|
builder.AddLogging(Logger.Setup);
|
||||||
|
|
||||||
var services = builder.BuildServiceProvider();
|
var services = builder.BuildServiceProvider();
|
||||||
return services;
|
return services;
|
||||||
@@ -66,6 +76,6 @@ class Program
|
|||||||
|
|
||||||
private static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
private static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
File.WriteAllText($"errorlog-{DateTime.Now:HHmmssfff}", e.ExceptionObject.ToString());
|
Logger.LogFatal(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using CommunityToolkit.Mvvm.Input;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.Models;
|
using Needlework.Net.Models;
|
||||||
using Needlework.Net.Services;
|
using Needlework.Net.Services;
|
||||||
@@ -19,20 +18,17 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.MainWindow;
|
namespace Needlework.Net.ViewModels.MainWindow;
|
||||||
|
|
||||||
public partial class MainWindowViewModel
|
public partial class MainWindowViewModel
|
||||||
: ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
|
: ObservableObject, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
|
||||||
{
|
{
|
||||||
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
|
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
|
||||||
[NotifyPropertyChangedFor(nameof(CurrentPage))]
|
|
||||||
[ObservableProperty] private NavigationViewItem _selectedMenuItem;
|
[ObservableProperty] private NavigationViewItem _selectedMenuItem;
|
||||||
public PageBase CurrentPage => (PageBase)SelectedMenuItem.Tag!;
|
[ObservableProperty] private PageBase _currentPage;
|
||||||
|
|
||||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||||
[ObservableProperty] private bool _isUpdateShown = false;
|
[ObservableProperty] private bool _isUpdateShown = false;
|
||||||
@@ -42,8 +38,8 @@ public partial class MainWindowViewModel
|
|||||||
|
|
||||||
public HttpClient HttpClient { get; }
|
public HttpClient HttpClient { get; }
|
||||||
public DialogService DialogService { get; }
|
public DialogService DialogService { get; }
|
||||||
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
|
|
||||||
public OpenApiDocument? HostDocument { get; set; }
|
private readonly DataSource _dataSource;
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
|
||||||
@@ -64,9 +60,10 @@ public partial class MainWindowViewModel
|
|||||||
};
|
};
|
||||||
private bool _isSchemaVersionChecked = false;
|
private bool _isSchemaVersionChecked = false;
|
||||||
|
|
||||||
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService, ILogger<MainWindowViewModel> logger)
|
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService, ILogger<MainWindowViewModel> logger, DataSource dataSource)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_dataSource = dataSource;
|
||||||
|
|
||||||
MenuItems = new AvaloniaList<NavigationViewItem>(pages
|
MenuItems = new AvaloniaList<NavigationViewItem>(pages
|
||||||
.OrderBy(p => p.Index)
|
.OrderBy(p => p.Index)
|
||||||
@@ -78,14 +75,13 @@ public partial class MainWindowViewModel
|
|||||||
IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") }
|
IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") }
|
||||||
}));
|
}));
|
||||||
SelectedMenuItem = MenuItems[0];
|
SelectedMenuItem = MenuItems[0];
|
||||||
|
CurrentPage = (PageBase)MenuItems[0].Tag!;
|
||||||
|
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
DialogService = dialogService;
|
DialogService = dialogService;
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||||
|
|
||||||
Task.Run(FetchDataAsync);
|
|
||||||
|
|
||||||
_latestUpdateTimer.Elapsed += OnLatestUpdateTimerElapsed;
|
_latestUpdateTimer.Elapsed += OnLatestUpdateTimerElapsed;
|
||||||
_schemaVersionTimer.Elapsed += OnSchemaVersionTimerElapsed;
|
_schemaVersionTimer.Elapsed += OnSchemaVersionTimerElapsed;
|
||||||
_latestUpdateTimer.Start();
|
_latestUpdateTimer.Start();
|
||||||
@@ -95,22 +91,34 @@ public partial class MainWindowViewModel
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedMenuItemChanged(NavigationViewItem value)
|
||||||
|
{
|
||||||
|
if (value.Tag is PageBase page)
|
||||||
|
{
|
||||||
|
CurrentPage = page;
|
||||||
|
if (!page.IsInitialized)
|
||||||
|
{
|
||||||
|
Task.Run(page.InitializeAsync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void OnSchemaVersionTimerElapsed(object? sender, ElapsedEventArgs? e)
|
private async void OnSchemaVersionTimerElapsed(object? sender, ElapsedEventArgs? e)
|
||||||
{
|
{
|
||||||
if (OpenApiDocumentWrapper == null) return;
|
|
||||||
if (!ProcessFinder.IsPortOpen()) return;
|
if (!ProcessFinder.IsPortOpen()) return;
|
||||||
|
var lcuSchemaDocument = await _dataSource.GetLcuSchemaDocumentAsync();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = Connector.GetLcuHttpClientInstance();
|
var client = Connector.GetLcuHttpClientInstance();
|
||||||
|
|
||||||
var currentSemVer = OpenApiDocumentWrapper.Info.Version.Split('.');
|
var currentSemVer = lcuSchemaDocument.Info.Version.Split('.');
|
||||||
var systemBuild = await client.GetFromJsonAsync<SystemBuild>("/system/v1/builds") ?? throw new NullReferenceException();
|
var systemBuild = await client.GetFromJsonAsync<SystemBuild>("/system/v1/builds") ?? throw new NullReferenceException();
|
||||||
var latestSemVer = systemBuild.Version.Split('.');
|
var latestSemVer = systemBuild.Version.Split('.');
|
||||||
|
|
||||||
if (!_isSchemaVersionChecked)
|
if (!_isSchemaVersionChecked)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("LCU Schema (current): {Version}", OpenApiDocumentWrapper.Info.Version);
|
_logger.LogInformation("LCU Schema (current): {Version}", lcuSchemaDocument.Info.Version);
|
||||||
_logger.LogInformation("LCU Schema (latest): {Version}", systemBuild.Version);
|
_logger.LogInformation("LCU Schema (latest): {Version}", systemBuild.Version);
|
||||||
_isSchemaVersionChecked = true;
|
_isSchemaVersionChecked = true;
|
||||||
}
|
}
|
||||||
@@ -177,34 +185,6 @@ public partial class MainWindowViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task FetchDataAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
|
||||||
HostDocument = document;
|
|
||||||
var handler = new OpenApiDocumentWrapper(document);
|
|
||||||
OpenApiDocumentWrapper = handler;
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
|
||||||
IsBusy = false;
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to fetch OpenAPI data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(DataRequestMessage message)
|
|
||||||
{
|
|
||||||
message.Reply(OpenApiDocumentWrapper!);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(HostDocumentRequestMessage message)
|
|
||||||
{
|
|
||||||
message.Reply(HostDocument!);
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenUrl(string url)
|
private void OpenUrl(string url)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages;
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
@@ -13,6 +14,12 @@ public partial class AboutViewModel : PageBase
|
|||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task InitializeAsync()
|
||||||
|
{
|
||||||
|
IsInitialized = true;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenUrl(string url)
|
private void OpenUrl(string url)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,41 +1,45 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using Needlework.Net.ViewModels.Shared;
|
using Needlework.Net.ViewModels.Shared;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages;
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
|
public partial class ConsoleViewModel : PageBase
|
||||||
{
|
{
|
||||||
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
||||||
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
|
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
[ObservableProperty] private LcuRequestViewModel _lcuRequest;
|
[ObservableProperty] private RequestViewModel _request;
|
||||||
|
|
||||||
public ConsoleViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) : base("Console", "terminal", -200)
|
private readonly DataSource _dataSource;
|
||||||
|
|
||||||
|
public ConsoleViewModel(ILogger<RequestViewModel> requestViewModelLogger, DataSource dataSource, HttpClient httpClient) : base("Console", "terminal", -200)
|
||||||
{
|
{
|
||||||
_lcuRequest = new(lcuRequestViewModelLogger);
|
_request = new(requestViewModelLogger, Endpoints.Tab.LCU, httpClient);
|
||||||
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
_dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
var document = await _dataSource.GetLcuSchemaDocumentAsync();
|
||||||
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
|
{
|
||||||
|
RequestPaths.Clear();
|
||||||
|
RequestPaths.AddRange(document.Paths);
|
||||||
|
});
|
||||||
|
IsBusy = false;
|
||||||
|
IsInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task SendRequest()
|
private async Task SendRequest()
|
||||||
{
|
{
|
||||||
await LcuRequest.ExecuteAsync();
|
await Request.ExecuteAsync();
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(DataReadyMessage message)
|
|
||||||
{
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
|
||||||
{
|
|
||||||
RequestPaths.Clear();
|
|
||||||
RequestPaths.AddRange(message.Value.Paths);
|
|
||||||
IsBusy = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using Needlework.Net.ViewModels.Shared;
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -23,12 +21,10 @@ public partial class EndpointViewModel : ObservableObject
|
|||||||
|
|
||||||
public event EventHandler<string>? PathOperationSelected;
|
public event EventHandler<string>? PathOperationSelected;
|
||||||
|
|
||||||
public EndpointViewModel(string endpoint, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
|
public EndpointViewModel(string endpoint, ILogger<RequestViewModel> requestViewModelLogger, Models.Document document, Tab tab, System.Net.Http.HttpClient httpClient)
|
||||||
{
|
{
|
||||||
Endpoint = endpoint;
|
Endpoint = endpoint;
|
||||||
|
PathOperations = new AvaloniaList<PathOperationViewModel>(document.Plugins[endpoint].Select(x => new PathOperationViewModel(x, requestViewModelLogger, document, tab, httpClient)));
|
||||||
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
|
|
||||||
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x, lcuRequestViewModelLogger)));
|
|
||||||
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,27 @@ public partial class EndpointsNavigationViewModel : ObservableObject
|
|||||||
|
|
||||||
[ObservableProperty] private ObservableObject _activeViewModel;
|
[ObservableProperty] private ObservableObject _activeViewModel;
|
||||||
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
||||||
[ObservableProperty] private string _title = string.Empty;
|
[ObservableProperty] private string _title;
|
||||||
|
|
||||||
private readonly Action<string?, Guid> _onEndpointNavigation;
|
private readonly Action<string?, Guid> _onEndpointNavigation;
|
||||||
|
private readonly Tab _tab;
|
||||||
|
|
||||||
public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
|
public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation, ILogger<RequestViewModel> requestViewModelLogger, Models.Document document, Tab tab, System.Net.Http.HttpClient httpClient)
|
||||||
{
|
{
|
||||||
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked, lcuRequestViewModelLogger);
|
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked, requestViewModelLogger, document, tab, httpClient);
|
||||||
_onEndpointNavigation = onEndpointNavigation;
|
_onEndpointNavigation = onEndpointNavigation;
|
||||||
|
_tab = tab;
|
||||||
|
_title = GetTitle(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetTitle(Tab tab)
|
||||||
|
{
|
||||||
|
return tab switch
|
||||||
|
{
|
||||||
|
Tab.LCU => "LCU",
|
||||||
|
Tab.GameClient => "Game Client",
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClicked(ObservableObject viewModel)
|
private void OnClicked(ObservableObject viewModel)
|
||||||
@@ -28,7 +41,7 @@ public partial class EndpointsNavigationViewModel : ObservableObject
|
|||||||
ActiveViewModel = viewModel;
|
ActiveViewModel = viewModel;
|
||||||
if (viewModel is EndpointViewModel endpoint)
|
if (viewModel is EndpointViewModel endpoint)
|
||||||
{
|
{
|
||||||
Title = endpoint.Title;
|
Title = $"{GetTitle(_tab)} - {endpoint.Title}";
|
||||||
_onEndpointNavigation.Invoke(endpoint.Title, Guid);
|
_onEndpointNavigation.Invoke(endpoint.Title, Guid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +50,7 @@ public partial class EndpointsNavigationViewModel : ObservableObject
|
|||||||
private void GoBack()
|
private void GoBack()
|
||||||
{
|
{
|
||||||
ActiveViewModel = EndpointsViewModel;
|
ActiveViewModel = EndpointsViewModel;
|
||||||
Title = string.Empty;
|
Title = GetTitle(_tab);
|
||||||
_onEndpointNavigation.Invoke(null, Guid);
|
_onEndpointNavigation.Invoke(null, Guid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,45 +2,64 @@
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Models;
|
||||||
using Needlework.Net.ViewModels.Shared;
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessage>
|
public enum Tab
|
||||||
|
{
|
||||||
|
LCU,
|
||||||
|
GameClient
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class EndpointsTabViewModel : PageBase
|
||||||
{
|
{
|
||||||
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
|
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
|
||||||
public IAvaloniaList<EndpointItem> Endpoints { get; } = new AvaloniaList<EndpointItem>();
|
public IAvaloniaList<EndpointItem> Endpoints { get; } = new AvaloniaList<EndpointItem>();
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
|
||||||
private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger;
|
private readonly ILogger<RequestViewModel> _requestViewModelLogger;
|
||||||
|
private readonly DataSource _dataSource;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
public EndpointsTabViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) : base("Endpoints", "list-alt", -500)
|
public EndpointsTabViewModel(ILogger<RequestViewModel> requestViewModelLogger, DataSource dataSource, IHttpClientFactory httpClientFactory) : base("Endpoints", "list-alt", -500)
|
||||||
{
|
{
|
||||||
_lcuRequestViewModelLogger = lcuRequestViewModelLogger;
|
_requestViewModelLogger = requestViewModelLogger;
|
||||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
_dataSource = dataSource;
|
||||||
|
_httpClient = httpClientFactory.CreateClient(nameof(EndpointsTabViewModel));
|
||||||
}
|
}
|
||||||
|
public override async Task InitializeAsync()
|
||||||
public void Receive(DataReadyMessage message)
|
|
||||||
{
|
{
|
||||||
|
await Dispatcher.UIThread.Invoke(async () => await AddEndpoint(Tab.LCU));
|
||||||
IsBusy = false;
|
IsBusy = false;
|
||||||
Plugins.Clear();
|
IsInitialized = true;
|
||||||
Plugins.AddRange(message.Value.Plugins.Keys);
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(AddEndpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void AddEndpoint()
|
private async Task AddEndpoint(Tab tab)
|
||||||
{
|
{
|
||||||
|
Document document = tab switch
|
||||||
|
{
|
||||||
|
Tab.LCU => await _dataSource.GetLcuSchemaDocumentAsync(),
|
||||||
|
Tab.GameClient => await _dataSource.GetLolClientDocumentAsync(),
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Plugins.Clear();
|
||||||
|
Plugins.AddRange(document.Plugins.Keys);
|
||||||
|
|
||||||
|
var vm = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation, _requestViewModelLogger, document, tab, _httpClient);
|
||||||
Endpoints.Add(new()
|
Endpoints.Add(new()
|
||||||
{
|
{
|
||||||
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation, _lcuRequestViewModelLogger),
|
Content = vm,
|
||||||
|
Header = vm.Title,
|
||||||
Selected = true
|
Selected = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -51,7 +70,7 @@ public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessa
|
|||||||
{
|
{
|
||||||
if (endpoint.Content.Guid.Equals(guid))
|
if (endpoint.Content.Guid.Equals(guid))
|
||||||
{
|
{
|
||||||
endpoint.Header = title ?? "Endpoints";
|
endpoint.Header = endpoint.Content.Title;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +79,7 @@ public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessa
|
|||||||
|
|
||||||
public partial class EndpointItem : ObservableObject
|
public partial class EndpointItem : ObservableObject
|
||||||
{
|
{
|
||||||
[ObservableProperty] private string _header = "Endpoints";
|
[ObservableProperty] private string _header = string.Empty;
|
||||||
public IconSource IconSource { get; set; } = new SymbolIconSource() { Symbol = Symbol.Document, FontSize = 20.0, Foreground = Avalonia.Media.Brushes.White };
|
public IconSource IconSource { get; set; } = new SymbolIconSource() { Symbol = Symbol.Document, FontSize = 20.0, Foreground = Avalonia.Media.Brushes.White };
|
||||||
public bool Selected { get; set; } = false;
|
public bool Selected { get; set; } = false;
|
||||||
public required EndpointsNavigationViewModel Content { get; init; }
|
public required EndpointsNavigationViewModel Content { get; init; }
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Needlework.Net.Models;
|
||||||
using Needlework.Net.ViewModels.Shared;
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
@@ -18,14 +20,20 @@ public partial class EndpointsViewModel : ObservableObject
|
|||||||
|
|
||||||
public Action<ObservableObject> OnClicked { get; }
|
public Action<ObservableObject> OnClicked { get; }
|
||||||
|
|
||||||
private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger;
|
private readonly ILogger<RequestViewModel> _requestViewModelLogger;
|
||||||
|
private readonly Document _document;
|
||||||
|
private readonly Tab _tab;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
|
public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked, ILogger<RequestViewModel> requestViewModelLogger, Models.Document document, Tab tab, System.Net.Http.HttpClient httpClient)
|
||||||
{
|
{
|
||||||
Plugins = new AvaloniaList<string>(plugins);
|
Plugins = new AvaloniaList<string>(plugins);
|
||||||
Query = new AvaloniaList<string>(plugins);
|
Query = new AvaloniaList<string>(plugins);
|
||||||
OnClicked = onClicked;
|
OnClicked = onClicked;
|
||||||
_lcuRequestViewModelLogger = lcuRequestViewModelLogger;
|
_requestViewModelLogger = requestViewModelLogger;
|
||||||
|
_document = document;
|
||||||
|
_tab = tab;
|
||||||
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSearchChanged(string value)
|
partial void OnSearchChanged(string value)
|
||||||
@@ -42,6 +50,6 @@ public partial class EndpointsViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value)) return;
|
if (string.IsNullOrEmpty(value)) return;
|
||||||
|
|
||||||
OnClicked.Invoke(new EndpointViewModel(value, _lcuRequestViewModelLogger));
|
OnClicked.Invoke(new EndpointViewModel(value, _requestViewModelLogger, _document, _tab, _httpClient));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Models;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
@@ -22,23 +22,23 @@ public partial class OperationViewModel : ObservableObject
|
|||||||
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
|
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
|
||||||
public string? RequestTemplate { get; }
|
public string? RequestTemplate { get; }
|
||||||
|
|
||||||
public OperationViewModel(OpenApiOperation operation)
|
public OperationViewModel(OpenApiOperation operation, Models.Document document)
|
||||||
{
|
{
|
||||||
Summary = operation.Summary ?? string.Empty;
|
Summary = operation.Summary ?? string.Empty;
|
||||||
Description = operation.Description ?? string.Empty;
|
Description = operation.Description ?? string.Empty;
|
||||||
IsRequestBody = operation.RequestBody != null;
|
IsRequestBody = operation.RequestBody != null;
|
||||||
ReturnType = GetReturnType(operation.Responses);
|
ReturnType = GetReturnType(operation.Responses);
|
||||||
RequestClasses = GetRequestClasses(operation.RequestBody);
|
RequestClasses = GetRequestClasses(operation.RequestBody, document);
|
||||||
ResponseClasses = GetResponseClasses(operation.Responses);
|
ResponseClasses = GetResponseClasses(operation.Responses, document);
|
||||||
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
|
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
|
||||||
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
|
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
|
||||||
RequestBodyType = GetRequestBodyType(operation.RequestBody);
|
RequestBodyType = GetRequestBodyType(operation.RequestBody);
|
||||||
RequestTemplate = GetRequestTemplate(operation.RequestBody);
|
RequestTemplate = GetRequestTemplate(operation.RequestBody, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? GetRequestTemplate(OpenApiRequestBody? requestBody)
|
private string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document)
|
||||||
{
|
{
|
||||||
var requestClasses = GetRequestClasses(requestBody);
|
var requestClasses = GetRequestClasses(requestBody, document);
|
||||||
if (requestClasses.Count == 0)
|
if (requestClasses.Count == 0)
|
||||||
{
|
{
|
||||||
var type = GetRequestBodyType(requestBody);
|
var type = GetRequestBodyType(requestBody);
|
||||||
@@ -133,17 +133,40 @@ public partial class OperationViewModel : ObservableObject
|
|||||||
return pathParameters;
|
return pathParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses)
|
private bool TryGetResponse(OpenApiResponses responses, [NotNullWhen(true)] out OpenApiResponse? response)
|
||||||
{
|
{
|
||||||
if (responses.TryGetValue("2XX", out var response)
|
response = null;
|
||||||
&& response.Content.TryGetValue("application/json", out var media))
|
var flag = false;
|
||||||
|
if (responses.TryGetValue("2XX", out var x))
|
||||||
{
|
{
|
||||||
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
response = x;
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
else if (responses.TryGetValue("200", out var y))
|
||||||
|
{
|
||||||
|
response = y;
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document document)
|
||||||
|
{
|
||||||
|
if (!TryGetResponse(responses, out var response))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
if (response.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var rawDocument = document.OpenApiDocument;
|
||||||
var schema = media.Schema;
|
var schema = media.Schema;
|
||||||
|
if (schema == null) return [];
|
||||||
|
|
||||||
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||||
WalkSchema(schema, propertyClasses, document);
|
WalkSchema(schema, propertyClasses, rawDocument);
|
||||||
return propertyClasses;
|
return propertyClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,12 +209,12 @@ public partial class OperationViewModel : ObservableObject
|
|||||||
|| type.Contains("number"));
|
|| type.Contains("number"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody)
|
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document document)
|
||||||
{
|
{
|
||||||
if (requestBody == null) return [];
|
if (requestBody == null) return [];
|
||||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||||
{
|
{
|
||||||
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
var rawDocument = document.OpenApiDocument;
|
||||||
var schema = media.Schema;
|
var schema = media.Schema;
|
||||||
if (schema == null) return [];
|
if (schema == null) return [];
|
||||||
|
|
||||||
@@ -199,9 +222,9 @@ public partial class OperationViewModel : ObservableObject
|
|||||||
if (IsComponent(type))
|
if (IsComponent(type))
|
||||||
{
|
{
|
||||||
var componentId = GetComponentId(schema);
|
var componentId = GetComponentId(schema);
|
||||||
var componentSchema = document.Components.Schemas[componentId];
|
var componentSchema = rawDocument.Components.Schemas[componentId];
|
||||||
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||||
WalkSchema(componentSchema, propertyClasses, document);
|
WalkSchema(componentSchema, propertyClasses, rawDocument);
|
||||||
return propertyClasses;
|
return propertyClasses;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,12 +233,15 @@ public partial class OperationViewModel : ObservableObject
|
|||||||
|
|
||||||
private string GetReturnType(OpenApiResponses responses)
|
private string GetReturnType(OpenApiResponses responses)
|
||||||
{
|
{
|
||||||
if (responses.TryGetValue("2XX", out var response)
|
if (!TryGetResponse(responses, out var response))
|
||||||
&& response.Content.TryGetValue("application/json", out var media))
|
return "none";
|
||||||
|
|
||||||
|
if (response.Content.TryGetValue("application/json", out var media))
|
||||||
{
|
{
|
||||||
var schema = media.Schema;
|
var schema = media.Schema;
|
||||||
return GetSchemaType(schema);
|
return GetSchemaType(schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +250,7 @@ public partial class OperationViewModel : ObservableObject
|
|||||||
if (schema.Reference != null) return schema.Reference.Id;
|
if (schema.Reference != null) return schema.Reference.Id;
|
||||||
if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||||
if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}";
|
if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}";
|
||||||
|
if (schema.Type == "array" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||||
if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]";
|
if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]";
|
||||||
if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]";
|
if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]";
|
||||||
if (schema.Type == "array") return $"{schema.Items.Type}[]";
|
if (schema.Type == "array") return $"{schema.Items.Type}[]";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Avalonia.Controls;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Models;
|
using Needlework.Net.Models;
|
||||||
@@ -18,13 +17,13 @@ public partial class PathOperationViewModel : ObservableObject
|
|||||||
public string Url { get; }
|
public string Url { get; }
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy;
|
[ObservableProperty] private bool _isBusy;
|
||||||
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest;
|
[ObservableProperty] private Lazy<RequestViewModel> _request;
|
||||||
|
|
||||||
public PathOperationViewModel(PathOperation pathOperation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
|
public PathOperationViewModel(PathOperation pathOperation, ILogger<RequestViewModel> requestViewModelLogger, Document document, Tab tab, System.Net.Http.HttpClient httpClient)
|
||||||
{
|
{
|
||||||
Path = pathOperation.Path;
|
Path = pathOperation.Path;
|
||||||
Operation = new OperationViewModel(pathOperation.Operation);
|
Operation = new OperationViewModel(pathOperation.Operation, document);
|
||||||
LcuRequest = new(() => new LcuRequestViewModel(lcuRequestViewModelLogger)
|
Request = new(() => new RequestViewModel(requestViewModelLogger, tab, httpClient)
|
||||||
{
|
{
|
||||||
Method = pathOperation.Method.ToUpper()
|
Method = pathOperation.Method.ToUpper()
|
||||||
});
|
});
|
||||||
@@ -51,8 +50,8 @@ public partial class PathOperationViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LcuRequest.Value.RequestPath = sb.ToString();
|
Request.Value.RequestPath = sb.ToString();
|
||||||
await LcuRequest.Value.ExecuteAsync();
|
await Request.Value.ExecuteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages;
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
@@ -14,6 +15,12 @@ public partial class HomeViewModel : PageBase
|
|||||||
|
|
||||||
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
||||||
|
|
||||||
|
public override Task InitializeAsync()
|
||||||
|
{
|
||||||
|
IsInitialized = true;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenUrl(string url)
|
private void OpenUrl(string url)
|
||||||
{
|
{
|
||||||
@@ -23,4 +30,5 @@ public partial class HomeViewModel : PageBase
|
|||||||
};
|
};
|
||||||
process.Start();
|
process.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages;
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
@@ -8,4 +9,7 @@ public abstract partial class PageBase(string displayName, string icon, int inde
|
|||||||
[ObservableProperty] private string _displayName = displayName;
|
[ObservableProperty] private string _displayName = displayName;
|
||||||
[ObservableProperty] private string _icon = icon;
|
[ObservableProperty] private string _icon = icon;
|
||||||
[ObservableProperty] private int _index = index;
|
[ObservableProperty] private int _index = index;
|
||||||
|
[ObservableProperty] private bool _isInitialized;
|
||||||
|
|
||||||
|
public abstract Task InitializeAsync();
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,12 @@ public partial class WebsocketViewModel : PageBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task InitializeAsync()
|
||||||
|
{
|
||||||
|
IsInitialized = true;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task InitializeEventTypes()
|
private async Task InitializeEventTypes()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.ViewModels.MainWindow;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
|
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -12,7 +13,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Shared;
|
namespace Needlework.Net.ViewModels.Shared;
|
||||||
|
|
||||||
public partial class LcuRequestViewModel : ObservableObject
|
public partial class RequestViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
[ObservableProperty] private string? _method = "GET";
|
[ObservableProperty] private string? _method = "GET";
|
||||||
[ObservableProperty] private SolidColorBrush _color = new(GetColor("GET"));
|
[ObservableProperty] private SolidColorBrush _color = new(GetColor("GET"));
|
||||||
@@ -29,14 +30,18 @@ public partial class LcuRequestViewModel : ObservableObject
|
|||||||
[ObservableProperty] private string? _responseAuthorization = null;
|
[ObservableProperty] private string? _responseAuthorization = null;
|
||||||
[ObservableProperty] private string? _responseBody = null;
|
[ObservableProperty] private string? _responseBody = null;
|
||||||
|
|
||||||
public event EventHandler<LcuRequestViewModel>? RequestText;
|
public event EventHandler<RequestViewModel>? RequestText;
|
||||||
public event EventHandler<string>? UpdateText;
|
public event EventHandler<string>? UpdateText;
|
||||||
|
|
||||||
private readonly ILogger<LcuRequestViewModel> _logger;
|
private readonly ILogger<RequestViewModel> _logger;
|
||||||
|
private readonly Tab _tab;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
public LcuRequestViewModel(ILogger<LcuRequestViewModel> logger)
|
public RequestViewModel(ILogger<RequestViewModel> logger, Pages.Endpoints.Tab tab, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_tab = tab;
|
||||||
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnMethodChanged(string? oldValue, string? newValue)
|
partial void OnMethodChanged(string? oldValue, string? newValue)
|
||||||
@@ -47,25 +52,80 @@ public partial class LcuRequestViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteAsync()
|
public async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
switch (_tab)
|
||||||
|
{
|
||||||
|
case Tab.LCU:
|
||||||
|
await ExecuteLcuAsync();
|
||||||
|
break;
|
||||||
|
case Tab.GameClient:
|
||||||
|
await ExecuteGameClientAsync();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteGameClientAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IsRequestBusy = true;
|
IsRequestBusy = true;
|
||||||
if (string.IsNullOrEmpty(RequestPath))
|
if (string.IsNullOrEmpty(RequestPath))
|
||||||
throw new Exception("Path is empty.");
|
throw new Exception("Path is empty.");
|
||||||
|
var method = GetMethod();
|
||||||
|
|
||||||
var method = Method switch
|
_logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath));
|
||||||
|
RequestText?.Invoke(this, this);
|
||||||
|
var content = new StringContent(RequestBody ?? string.Empty, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||||
|
var responsePath = $"https://127.0.0.1:2999{RequestPath}";
|
||||||
|
var response = await _httpClient.SendAsync(new HttpRequestMessage(method, responsePath) { Content = content });
|
||||||
|
var responseBody = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
|
||||||
|
var body = responseBody.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
|
||||||
|
if (body.Length > App.MaxCharacters)
|
||||||
{
|
{
|
||||||
"GET" => HttpMethod.Get,
|
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body));
|
||||||
"POST" => HttpMethod.Post,
|
UpdateText?.Invoke(this, string.Empty);
|
||||||
"PUT" => HttpMethod.Put,
|
}
|
||||||
"DELETE" => HttpMethod.Delete,
|
else
|
||||||
"HEAD" => HttpMethod.Head,
|
{
|
||||||
"PATCH" => HttpMethod.Patch,
|
ResponseBody = body;
|
||||||
"OPTIONS" => HttpMethod.Options,
|
UpdateText?.Invoke(this, body);
|
||||||
"TRACE" => HttpMethod.Trace,
|
}
|
||||||
_ => throw new Exception("Method is not selected or missing."),
|
|
||||||
};
|
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
|
||||||
|
ResponsePath = responsePath;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Request failed: {Tuple}", (Method, RequestPath));
|
||||||
|
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
||||||
|
UpdateText?.Invoke(this, string.Empty);
|
||||||
|
|
||||||
|
ResponseStatus = null;
|
||||||
|
ResponsePath = null;
|
||||||
|
ResponseAuthentication = null;
|
||||||
|
ResponseAuthorization = null;
|
||||||
|
ResponseUsername = null;
|
||||||
|
ResponsePassword = null;
|
||||||
|
ResponseBody = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsRequestBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteLcuAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsRequestBusy = true;
|
||||||
|
if (string.IsNullOrEmpty(RequestPath))
|
||||||
|
throw new Exception("Path is empty.");
|
||||||
|
var method = GetMethod();
|
||||||
|
|
||||||
_logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath));
|
_logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath));
|
||||||
|
|
||||||
@@ -116,6 +176,22 @@ public partial class LcuRequestViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpMethod GetMethod()
|
||||||
|
{
|
||||||
|
return Method switch
|
||||||
|
{
|
||||||
|
"GET" => HttpMethod.Get,
|
||||||
|
"POST" => HttpMethod.Post,
|
||||||
|
"PUT" => HttpMethod.Put,
|
||||||
|
"DELETE" => HttpMethod.Delete,
|
||||||
|
"HEAD" => HttpMethod.Head,
|
||||||
|
"PATCH" => HttpMethod.Patch,
|
||||||
|
"OPTIONS" => HttpMethod.Options,
|
||||||
|
"TRACE" => HttpMethod.Trace,
|
||||||
|
_ => throw new Exception("Method is not selected or missing."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static Color GetColor(string method) => method switch
|
private static Color GetColor(string method) => method switch
|
||||||
{
|
{
|
||||||
"GET" => Avalonia.Media.Color.FromRgb(95, 99, 186),
|
"GET" => Avalonia.Media.Color.FromRgb(95, 99, 186),
|
||||||
@@ -8,160 +8,46 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.Pages.AboutView"
|
x:Class="Needlework.Net.Views.Pages.AboutView"
|
||||||
x:DataType="vm:AboutViewModel">
|
x:DataType="vm:AboutViewModel">
|
||||||
<UserControl.Styles>
|
<ScrollViewer Margin="8">
|
||||||
<Style Selector="Button">
|
<WrapPanel HorizontalAlignment="Center">
|
||||||
<Setter Property="Theme" Value="{StaticResource TransparentButton}"/>
|
<WrapPanel.Styles>
|
||||||
<Setter Property="Command" Value="{Binding OpenUrlCommand}"/>
|
<Style Selector="controls|UserCard">
|
||||||
</Style>
|
<Setter Property="Width" Value="272"/>
|
||||||
<Style Selector="i|Icon">
|
<Setter Property="MaxHeight" Value="378"/>
|
||||||
<Setter Property="FontSize" Value="20" />
|
<Setter Property="Margin" Value="0 16 32 16"/>
|
||||||
</Style>
|
</Style>
|
||||||
</UserControl.Styles>
|
</WrapPanel.Styles>
|
||||||
<Grid Margin="8"
|
<controls:UserCard UserImage="/Assets/Users/blossomishymae.png"
|
||||||
VerticalAlignment="Center"
|
UserName="estrogen elf"
|
||||||
HorizontalAlignment="Center">
|
UserGithub="BlossomiShymae">
|
||||||
<StackPanel Spacing="8">
|
Needlework.Net is the .NET rewrite of Needlework. This tool was made to help others with LCU and Game Client development. Feel free to ask any questions
|
||||||
<Grid HorizontalAlignment="Center">
|
or help contribute to the project! Made with love. 💜
|
||||||
<StackPanel Orientation="Horizontal">
|
</controls:UserCard>
|
||||||
<controls:Card Margin="8">
|
<controls:UserCard UserImage="/Assets/Users/dysolix.png"
|
||||||
<Image Source="/Assets/Users/blossomishymae.png"
|
UserName="dysolix"
|
||||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
UserGithub="dysolix">
|
||||||
Width="200"
|
For providing LCU Schema, the auto-generated OpenAPI document for the LCU.
|
||||||
Height="200"/>
|
</controls:UserCard>
|
||||||
</controls:Card>
|
<controls:UserCard UserImage="/Assets/Users/sylv.jpg"
|
||||||
<StackPanel Margin="8 0 0 0">
|
UserName="Sylv"
|
||||||
<controls:Card Width="400" Margin="8">
|
UserGithub="AlsoSylv">
|
||||||
<StackPanel Orientation="Horizontal">
|
For providing a fixed up-to-date Game Client schema.
|
||||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
</controls:UserCard>
|
||||||
Margin="0 0 8 0">Blossomi Shymae</TextBlock>
|
<controls:UserCard UserImage="/Assets/Users/ray.png"
|
||||||
<Button CommandParameter="https://github.com/BlossomiShymae">
|
UserName="Ray"
|
||||||
<i:Icon Value="fa-github"/>
|
UserGithub="Hi-Ray">
|
||||||
</Button>
|
For guidance, advice, and providing help via HextechDocs.
|
||||||
</StackPanel>
|
</controls:UserCard>
|
||||||
</controls:Card>
|
<controls:UserCard UserImage="/Assets/Users/dubble.png"
|
||||||
<controls:Card Width="400" Margin="8">
|
UserName="dubble"
|
||||||
<StackPanel >
|
UserGithub="cuppachino">
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">About</TextBlock>
|
For encouraging me to publish Needlework.Net and other ideas.
|
||||||
<TextBlock TextWrapping="Wrap">
|
</controls:UserCard>
|
||||||
Needlework.Net is the .NET rewrite of Needlework. This tool was made to help others with LCU development. Feel free to ask any questions
|
<controls:UserCard UserImage="/Assets/Users/aoshiw.png"
|
||||||
or help contribute to the project! Made with love. 💜
|
UserName="AoshiW"
|
||||||
</TextBlock>
|
UserGithub="AoshiW">
|
||||||
</StackPanel>
|
For PR.
|
||||||
</controls:Card>
|
</controls:UserCard>
|
||||||
</StackPanel>
|
</WrapPanel>
|
||||||
</StackPanel>
|
</ScrollViewer>
|
||||||
</Grid>
|
|
||||||
<Border Width="800">
|
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Thanks to the friends and people who made this tool possible...</TextBlock>
|
|
||||||
</Border>
|
|
||||||
<WrapPanel Orientation="Horizontal">
|
|
||||||
<StackPanel Orientation="Horizontal"
|
|
||||||
Margin="8">
|
|
||||||
<controls:Card>
|
|
||||||
<Image Source="/Assets/Users/dysolix.png"
|
|
||||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
|
||||||
Width="100"
|
|
||||||
Height="100"/>
|
|
||||||
</controls:Card>
|
|
||||||
<StackPanel Margin="2 0 0 0">
|
|
||||||
<controls:Card Width="250" Margin="2">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
|
||||||
Margin="0 0 8 0">dysolix</TextBlock>
|
|
||||||
<Button CommandParameter="https://github.com/dysolix">
|
|
||||||
<i:Icon Value="fa-github"/>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
|
||||||
<controls:Card Width="250" Margin="2">
|
|
||||||
<StackPanel >
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
|
||||||
For providing and hosting an auto-generated OpenAPI document of the LCU.
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Orientation="Horizontal"
|
|
||||||
Margin="8">
|
|
||||||
<controls:Card>
|
|
||||||
<Image Source="/Assets/Users/ray.png"
|
|
||||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
|
||||||
Width="100"
|
|
||||||
Height="100"/>
|
|
||||||
</controls:Card>
|
|
||||||
<StackPanel Margin="2 0 0 0">
|
|
||||||
<controls:Card Width="250" Margin="2">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
|
||||||
Margin="0 0 8 0">Ray</TextBlock>
|
|
||||||
<Button CommandParameter="https://github.com/Hi-Ray">
|
|
||||||
<i:Icon Value="fa-github"/>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
|
||||||
<controls:Card Width="250" Margin="2">
|
|
||||||
<StackPanel >
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
|
||||||
For guidance, advice, or providing help via HextechDocs.
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Orientation="Horizontal"
|
|
||||||
Margin="8">
|
|
||||||
<controls:Card>
|
|
||||||
<Image Source="/Assets/Users/dubble.png"
|
|
||||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
|
||||||
Width="100"
|
|
||||||
Height="100"/>
|
|
||||||
</controls:Card>
|
|
||||||
<StackPanel Margin="4 0 0 0">
|
|
||||||
<controls:Card Width="250" Margin="2">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
|
||||||
Margin="0 0 8 0">dubble</TextBlock>
|
|
||||||
<Button CommandParameter="https://github.com/cuppachino">
|
|
||||||
<i:Icon Value="fa-github"/>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
|
||||||
<controls:Card Width="250" Margin="2">
|
|
||||||
<StackPanel >
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
|
||||||
For encouraging me to publish Needlework. This project may never have seen the light of day without him.
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Orientation="Horizontal"
|
|
||||||
Margin="8">
|
|
||||||
<controls:Card>
|
|
||||||
<Image Source="/Assets/Users/community.png"
|
|
||||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
|
||||||
Width="100"
|
|
||||||
Height="100"/>
|
|
||||||
</controls:Card>
|
|
||||||
<StackPanel Margin="4 0 0 0">
|
|
||||||
<controls:Card Width="250" Margin="2">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
|
||||||
Width="250"
|
|
||||||
TextWrapping="Wrap">Third Party Developer Community</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
|
||||||
<controls:Card Width="250" Margin="2">
|
|
||||||
<StackPanel >
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
|
||||||
For providing numerous documentation on the LCU.
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</WrapPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -16,16 +16,16 @@
|
|||||||
<Grid Grid.Row="0"
|
<Grid Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="2">
|
Grid.ColumnSpan="2">
|
||||||
<StackPanel Margin="0 0 0 16">
|
<StackPanel Margin="0 0 0 8">
|
||||||
<Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto">
|
<Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto">
|
||||||
<ComboBox ItemsSource="{Binding RequestMethods}"
|
<ComboBox ItemsSource="{Binding RequestMethods}"
|
||||||
SelectedItem="{Binding LcuRequest.Method}"
|
SelectedItem="{Binding Request.Method}"
|
||||||
Margin="0 0 8 0"
|
Margin="0 0 8 0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"/>
|
Grid.Column="0"/>
|
||||||
<AutoCompleteBox
|
<AutoCompleteBox
|
||||||
ItemsSource="{Binding RequestPaths}"
|
ItemsSource="{Binding RequestPaths}"
|
||||||
Text="{Binding LcuRequest.RequestPath}"
|
Text="{Binding Request.RequestPath}"
|
||||||
MaxDropDownHeight="400"
|
MaxDropDownHeight="400"
|
||||||
FilterMode="StartsWith"
|
FilterMode="StartsWith"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<TextBox IsReadOnly="True"
|
<TextBox IsReadOnly="True"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Text="{Binding LcuRequest.ResponsePath}"/>
|
Text="{Binding Request.ResponsePath}"/>
|
||||||
<avaloniaEdit:TextEditor
|
<avaloniaEdit:TextEditor
|
||||||
Name="RequestEditor"
|
Name="RequestEditor"
|
||||||
Text=""
|
Text=""
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
<Button Content="{Binding LcuRequest.ResponseStatus}"
|
<Button Content="{Binding Request.ResponseStatus}"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ public partial class ConsoleView : UserControl
|
|||||||
_requestEditor?.ApplyJsonEditorSettings();
|
_requestEditor?.ApplyJsonEditorSettings();
|
||||||
|
|
||||||
var vm = (ConsoleViewModel)DataContext!;
|
var vm = (ConsoleViewModel)DataContext!;
|
||||||
vm.LcuRequest.RequestText += LcuRequest_RequestText; ;
|
vm.Request.RequestText += LcuRequest_RequestText; ;
|
||||||
vm.LcuRequest.UpdateText += LcuRequest_UpdateText;
|
vm.Request.UpdateText += LcuRequest_UpdateText;
|
||||||
|
|
||||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LcuRequest_RequestText(object? sender, ViewModels.Shared.LcuRequestViewModel e)
|
private void LcuRequest_RequestText(object? sender, ViewModels.Shared.RequestViewModel e)
|
||||||
{
|
{
|
||||||
e.RequestBody = _requestEditor!.Text;
|
e.RequestBody = _requestEditor!.Text;
|
||||||
}
|
}
|
||||||
@@ -49,8 +49,8 @@ public partial class ConsoleView : UserControl
|
|||||||
base.OnDetachedFromVisualTree(e);
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
var vm = (ConsoleViewModel)DataContext!;
|
var vm = (ConsoleViewModel)DataContext!;
|
||||||
vm.LcuRequest.RequestText -= LcuRequest_RequestText;
|
vm.Request.RequestText -= LcuRequest_RequestText;
|
||||||
vm.LcuRequest.UpdateText -= LcuRequest_UpdateText;
|
vm.Request.UpdateText -= LcuRequest_UpdateText;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||||
|
|||||||
@@ -64,8 +64,8 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
Margin="0 0 8 0"
|
Margin="0 0 8 0"
|
||||||
Text="{Binding LcuRequest.Value.Method}"
|
Text="{Binding Request.Value.Method}"
|
||||||
Background="{Binding LcuRequest.Value.Color}"
|
Background="{Binding Request.Value.Color}"
|
||||||
FontSize="8"
|
FontSize="8"
|
||||||
Width="50"
|
Width="50"
|
||||||
Padding="10 2 10 2"
|
Padding="10 2 10 2"
|
||||||
@@ -93,14 +93,14 @@
|
|||||||
ColumnDefinitions="auto,*,auto">
|
ColumnDefinitions="auto,*,auto">
|
||||||
<TextBox Grid.Row="0"
|
<TextBox Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.Method}"
|
Text="{Binding SelectedPathOperation.Request.Value.Method}"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Margin="0 0 8 0"/>
|
Margin="0 0 8 0"/>
|
||||||
<TextBox Grid.Row="0"
|
<TextBox Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponsePath}"
|
Text="{Binding SelectedPathOperation.Request.Value.ResponsePath}"
|
||||||
IsReadOnly="True"/>
|
IsReadOnly="True"/>
|
||||||
<StackPanel Grid.Row="0"
|
<StackPanel Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="0 0 0 8"
|
Margin="0 0 0 8"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponseUsername}" />
|
Text="{Binding SelectedPathOperation.Request.Value.ResponseUsername}" />
|
||||||
<TextBlock FontSize="12"
|
<TextBlock FontSize="12"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="0 0 0 8"
|
Margin="0 0 0 8"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponsePassword}"/>
|
Text="{Binding SelectedPathOperation.Request.Value.ResponsePassword}"/>
|
||||||
<TextBlock FontSize="12"
|
<TextBlock FontSize="12"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponseAuthorization}"/>
|
Text="{Binding SelectedPathOperation.Request.Value.ResponseAuthorization}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Schemas">
|
<TabItem Header="Schemas">
|
||||||
@@ -309,7 +309,7 @@
|
|||||||
FontSize="10"
|
FontSize="10"
|
||||||
Padding="12 4 12 4"
|
Padding="12 4 12 4"
|
||||||
Classes="Flat"
|
Classes="Flat"
|
||||||
Content="{Binding SelectedPathOperation.LcuRequest.Value.ResponseStatus}"/>
|
Content="{Binding SelectedPathOperation.Request.Value.ResponseStatus}"/>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Grid Grid.Row="1" Grid.Column="4">
|
<Grid Grid.Row="1" Grid.Column="4">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public partial class EndpointView : UserControl
|
|||||||
{
|
{
|
||||||
private TextEditor? _requestEditor;
|
private TextEditor? _requestEditor;
|
||||||
private TextEditor? _responseEditor;
|
private TextEditor? _responseEditor;
|
||||||
private LcuRequestViewModel? _lcuRequestVm;
|
private RequestViewModel? _lcuRequestVm;
|
||||||
|
|
||||||
public EndpointView()
|
public EndpointView()
|
||||||
{
|
{
|
||||||
@@ -34,9 +34,9 @@ public partial class EndpointView : UserControl
|
|||||||
|
|
||||||
if (vm.SelectedPathOperation != null)
|
if (vm.SelectedPathOperation != null)
|
||||||
{
|
{
|
||||||
_lcuRequestVm = vm.SelectedPathOperation.LcuRequest.Value;
|
_lcuRequestVm = vm.SelectedPathOperation.Request.Value;
|
||||||
vm.SelectedPathOperation.LcuRequest.Value.RequestText += LcuRequest_RequestText;
|
vm.SelectedPathOperation.Request.Value.RequestText += LcuRequest_RequestText;
|
||||||
vm.SelectedPathOperation.LcuRequest.Value.UpdateText += LcuRequest_UpdateText;
|
vm.SelectedPathOperation.Request.Value.UpdateText += LcuRequest_UpdateText;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||||
@@ -53,10 +53,10 @@ public partial class EndpointView : UserControl
|
|||||||
_lcuRequestVm.RequestText -= LcuRequest_RequestText;
|
_lcuRequestVm.RequestText -= LcuRequest_RequestText;
|
||||||
_lcuRequestVm.UpdateText -= LcuRequest_UpdateText;
|
_lcuRequestVm.UpdateText -= LcuRequest_UpdateText;
|
||||||
}
|
}
|
||||||
vm.SelectedPathOperation.LcuRequest.Value.RequestText += LcuRequest_RequestText;
|
vm.SelectedPathOperation.Request.Value.RequestText += LcuRequest_RequestText;
|
||||||
vm.SelectedPathOperation.LcuRequest.Value.UpdateText += LcuRequest_UpdateText;
|
vm.SelectedPathOperation.Request.Value.UpdateText += LcuRequest_UpdateText;
|
||||||
_lcuRequestVm = vm.SelectedPathOperation.LcuRequest.Value;
|
_lcuRequestVm = vm.SelectedPathOperation.Request.Value;
|
||||||
_responseEditor!.Text = vm.SelectedPathOperation.LcuRequest.Value.ResponseBody ?? string.Empty;
|
_responseEditor!.Text = vm.SelectedPathOperation.Request.Value.ResponseBody ?? string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ public partial class EndpointView : UserControl
|
|||||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LcuRequest_RequestText(object? sender, LcuRequestViewModel e)
|
private void LcuRequest_RequestText(object? sender, RequestViewModel e)
|
||||||
{
|
{
|
||||||
e.RequestBody = _requestEditor!.Text;
|
e.RequestBody = _requestEditor!.Text;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
|
Name="EndpointsTab"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
<ui:TabView TabItems="{Binding Endpoints}"
|
<ui:TabView TabItems="{Binding Endpoints}"
|
||||||
AddTabButtonCommand="{Binding AddEndpointCommand}"
|
AddTabButtonCommand="{Binding AddEndpointCommand}"
|
||||||
|
AddTabButtonCommandParameter="{x:Static vm:Tab.LCU}"
|
||||||
TabCloseRequested="TabView_TabCloseRequested">
|
TabCloseRequested="TabView_TabCloseRequested">
|
||||||
<!--Need to override Tab header for Mica theme...-->
|
<!--Need to override Tab header for Mica theme...-->
|
||||||
<ui:TabView.Resources>
|
<ui:TabView.Resources>
|
||||||
@@ -44,7 +46,24 @@
|
|||||||
Content="{Binding}">
|
Content="{Binding}">
|
||||||
<ui:TabViewItem.ContentTemplate>
|
<ui:TabViewItem.ContentTemplate>
|
||||||
<DataTemplate DataType="vm:EndpointItem">
|
<DataTemplate DataType="vm:EndpointItem">
|
||||||
<ContentControl Content="{Binding Content}"/>
|
<Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||||
|
<Menu Grid.Row="0"
|
||||||
|
Grid.Column="0">
|
||||||
|
<MenuItem Header="_New tab">
|
||||||
|
<MenuItem Header="LCU"
|
||||||
|
Command="{Binding #EndpointsTab.((vm:EndpointsTabViewModel)DataContext).AddEndpointCommand}"
|
||||||
|
CommandParameter="{x:Static vm:Tab.LCU}"/>
|
||||||
|
<MenuItem Header="Game Client"
|
||||||
|
Command="{Binding #EndpointsTab.((vm:EndpointsTabViewModel)DataContext).AddEndpointCommand}"
|
||||||
|
CommandParameter="{x:Static vm:Tab.GameClient}"/>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
<Separator Grid.Row="1"
|
||||||
|
Grid.Column="0"/>
|
||||||
|
<ContentControl Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Content="{Binding Content}"/>
|
||||||
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ui:TabViewItem.ContentTemplate>
|
</ui:TabViewItem.ContentTemplate>
|
||||||
</ui:TabViewItem>
|
</ui:TabViewItem>
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ public partial class EndpointsTabView : UserControl
|
|||||||
|
|
||||||
private void TabView_TabCloseRequested(FluentAvalonia.UI.Controls.TabView sender, FluentAvalonia.UI.Controls.TabViewTabCloseRequestedEventArgs args)
|
private void TabView_TabCloseRequested(FluentAvalonia.UI.Controls.TabView sender, FluentAvalonia.UI.Controls.TabViewTabCloseRequestedEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Tab.Content is EndpointItem item)
|
if (args.Tab.Content is EndpointItem item && sender.TabItems is IList tabItems)
|
||||||
((IList)sender.TabItems).Remove(item);
|
{
|
||||||
|
if (tabItems.Count > 1)
|
||||||
|
{
|
||||||
|
tabItems.Remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">
|
||||||
Welcome to Needlework.Net
|
Welcome to Needlework.Net
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock>Get started with LCU development by clicking on the endpoints tab in the left panel.</TextBlock>
|
<TextBlock>Get started with LCU or Game Client development by clicking on the endpoints tab in the left panel.</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<controls:Card Margin="12">
|
<controls:Card Margin="12">
|
||||||
@@ -66,6 +66,9 @@
|
|||||||
<Button CommandParameter="https://discord.com/channels/187652476080488449/516802588805431296" Margin="4">
|
<Button CommandParameter="https://discord.com/channels/187652476080488449/516802588805431296" Margin="4">
|
||||||
#lcu-api
|
#lcu-api
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button CommandParameter="https://discord.com/channels/187652476080488449/543112946402721832" Margin="4">
|
||||||
|
#ingame-api
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</controls:Card>
|
</controls:Card>
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Needlework.Net is an open-source helper tool for the LCU that provides documented endpoints and can send requests without any code setup. Created using .NET! 🌠
|
Needlework.Net is an open-source helper tool for the LCU and Game Client that provides documented endpoints and can send requests without any code setup. Created using .NET! 🌠
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Interactive OpenAPI documentation
|
- Interactive OpenAPI documentations
|
||||||
- REST data transfer console
|
- REST data transfer console
|
||||||
- WebSocket event data viewer
|
- WebSocket event data viewer
|
||||||
|
|
||||||
@@ -39,9 +39,14 @@ This project was inspired by LCU Explorer, an application created by the Hextech
|
|||||||
|
|
||||||
### hasagi-types
|
### hasagi-types
|
||||||
|
|
||||||
Endpoints and schemas are provided by dysolix's [generated OpenAPI file.](https://raw.githubusercontent.com/dysolix/hasagi-types/main/swagger.json) Thank you!
|
LCU Schema endpoints are provided by dysolix's [generated OpenAPI file.](https://raw.githubusercontent.com/dysolix/hasagi-types/main/swagger.json) Thank you!
|
||||||
- [Repository](https://github.com/dysolix/hasagi-types)
|
- [Repository](https://github.com/dysolix/hasagi-types)
|
||||||
|
|
||||||
|
### Irelia
|
||||||
|
|
||||||
|
Game Client endpoints are provided by AlsoSylv's [fixed OpenAPI file.](https://raw.githubusercontent.com/AlsoSylv/Irelia/refs/heads/master/schemas/game_schema.json) Thank you despite not wanting to receive credit. :pout:
|
||||||
|
- [Repository](https://github.com/AlsoSylv/Irelia)
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
THE PROGRAM IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGMENT, OR OF FITNESS FOR A PARTICULAR PURPOSE. LICENSOR DOES NOT WARRANT THAT THE FUNCTIONS CONTAINED IN THE PROGRAM WILL MEET YOUR REQUIREMENTS OR THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. LICENSOR MAKES NO WARRANTIES RESPECTING ANY HARM THAT MAY BE CAUSED BY MALICIOUS USE OF THIS SOFTWARE. LICENSOR FURTHER EXPRESSLY DISCLAIMS ANY WARRANTY OR REPRESENTATION TO AUTHORIZED USERS OR TO ANY THIRD PARTY.
|
THE PROGRAM IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGMENT, OR OF FITNESS FOR A PARTICULAR PURPOSE. LICENSOR DOES NOT WARRANT THAT THE FUNCTIONS CONTAINED IN THE PROGRAM WILL MEET YOUR REQUIREMENTS OR THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. LICENSOR MAKES NO WARRANTIES RESPECTING ANY HARM THAT MAY BE CAUSED BY MALICIOUS USE OF THIS SOFTWARE. LICENSOR FURTHER EXPRESSLY DISCLAIMS ANY WARRANTY OR REPRESENTATION TO AUTHORIZED USERS OR TO ANY THIRD PARTY.
|
||||||
|
|||||||
Reference in New Issue
Block a user