This commit is contained in:
BlossomiShymae
2024-08-02 02:25:17 -05:00
parent b9b067a1c5
commit a8741cd352
41 changed files with 1051 additions and 53 deletions

View File

@@ -0,0 +1,27 @@
using Xunit.Abstractions;
namespace Needlework.Net.Core.Tests;
public class LcuSchemaHandlerTest
{
private readonly ITestOutputHelper _output;
internal HttpClient HttpClient { get; } = new();
public LcuSchemaHandlerTest(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public async Task PluginsTestAsync()
{
var reader = new LcuSchemaHandler(await Resources.GetOpenApiDocumentAsync(HttpClient));
var plugins = reader.Plugins.Keys.ToList();
foreach (var plugin in plugins)
_output.WriteLine($"Plugin: {plugin}");
Assert.True(plugins.Count > 0);
}
}

View File

@@ -0,0 +1,25 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Xunit.Abstractions;
namespace Needlework.Net.Core.Tests;
public class ResourcesTest
{
private readonly ITestOutputHelper _output;
internal HttpClient HttpClient { get; } = new();
public ResourcesTest(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public async Task DocumentTestAsync()
{
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
Assert.True(document.Info.Title == "LCU SCHEMA");
}
}

View File

@@ -1,10 +0,0 @@
namespace Needlework.Net.Core.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}

View File

@@ -1,6 +0,0 @@
namespace Needlework.Net.Core;
public class Class1
{
}

View File

@@ -0,0 +1,10 @@
using BlossomiShymae.GrrrLCU;
namespace Needlework.Net.Core;
public static class LcuConnector
{
public static Func<ProcessInfo> GetProcessInfo { get; } = Connector.GetProcessInfo;
public static Func<int, string, Uri> GetLeagueClientUri { get; } = Connector.GetLeagueClientUri;
public static Func<HttpMethod, string, CancellationToken, Task<HttpResponseMessage>> SendAsync { get; } = Connector.SendAsync;
}

View File

@@ -0,0 +1,49 @@
using Microsoft.OpenApi.Models;
namespace Needlework.Net.Core;
public class LcuSchemaHandler
{
internal OpenApiDocument OpenApiDocument { get; }
public SortedDictionary<string, OpenApiPathItem> Plugins { get; } = [];
public OpenApiInfo Info => OpenApiDocument.Info;
public LcuSchemaHandler(OpenApiDocument openApiDocument)
{
OpenApiDocument = openApiDocument;
// Group paths by plugins
foreach (var tag in OpenApiDocument.Tags)
{
foreach (var path in OpenApiDocument.Paths)
{
var containsTag = false;
var sentinelTag = string.Empty;
foreach (var operation in path.Value.Operations)
{
foreach (var operationTag in operation.Value.Tags)
{
var lhs = tag.Name.Replace("Plugin ", string.Empty);
var rhs = operationTag.Name.Replace("Plugin ", string.Empty);
if (lhs.Equals(rhs, StringComparison.OrdinalIgnoreCase))
{
containsTag = true;
sentinelTag = lhs.ToLower();
break; // Break early since all operations in a path share the same tags
}
}
if (containsTag)
break; // Ditto
}
if (containsTag)
Plugins[sentinelTag] = path.Value;
}
}
}
}

View File

@@ -6,4 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BlossomiShymae.GrrrLCU" Version="0.4.0" />
<PackageReference Include="Microsoft.OpenApi" Version="1.6.16" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.16" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
namespace Needlework.Net.Core;
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;
}
}

View File

@@ -1,10 +1,19 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Needlework.Net.Desktop.App"
RequestedThemeVariant="Default">
RequestedThemeVariant="Dark"
xmlns:local="using:Needlework.Net.Desktop"
xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme></FluentTheme>
<sukiUi:SukiTheme ThemeColor="Blue" />
<materialIcons:MaterialIconStyles />
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
</Application.Styles>
</Application>

View File

@@ -1,11 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using Needlework.Net.Desktop.ViewModels;
using Needlework.Net.Desktop.Views;
using System;
using System.Text.Json;
namespace Needlework.Net.Desktop;
public partial class App : Application
public partial class App(IServiceProvider serviceProvider) : Application
{
private readonly IServiceProvider _serviceProvider = serviceProvider;
public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
{
WriteIndented = true
};
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
@@ -15,7 +27,10 @@ public partial class App : Application
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
desktop.MainWindow = new MainWindow()
{
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
};
}
base.OnFrameworkInitializationCompleted();

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View File

@@ -1,9 +0,0 @@
<Window 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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Needlework.Net.Desktop.MainWindow"
Title="Needlework.Net.Desktop">
Welcome to Avalonia!
</Window>

View File

@@ -1,11 +0,0 @@
using Avalonia.Controls;
namespace Needlework.Net.Desktop;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

View File

@@ -1,23 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<PropertyGroup Label="Avalonia">
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
<AssemblyVersion>0.1.0.0</AssemblyVersion>
<FileVersion>0.1.0.0</FileVersion>
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.0" />
<PackageReference Include="Avalonia.Desktop" Version="11.1.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.0" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.0" />
<PackageReference Include="Avalonia" Version="11.1.0-beta2" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
<PackageReference Include="Avalonia.Desktop" Version="11.1.0-beta2" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.0-beta2" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0-beta2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.0-beta2" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Projektanker.Icons.Avalonia" Version="9.4.0" />
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.0" />
<PackageReference Include="SukiUI" Version="6.0.0-beta7" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.60" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Needlework.Net.Core\Needlework.Net.Core.csproj" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Views\AboutView.axaml" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,11 @@
using Avalonia;
using Microsoft.Extensions.DependencyInjection;
using Needlework.Net.Desktop.Services;
using Needlework.Net.Desktop.ViewModels;
using Projektanker.Icons.Avalonia;
using Projektanker.Icons.Avalonia.FontAwesome;
using System;
using System.Linq;
namespace Needlework.Net.Desktop;
@@ -14,8 +20,32 @@ class Program
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
{
IconProvider.Current
.Register<FontAwesomeIconProvider>();
return AppBuilder.Configure(() => new App(BuildServices()))
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
private static IServiceProvider BuildServices()
{
var builder = new ServiceCollection();
builder.AddSingleton<MainWindowViewModel>();
builder.AddSingleton<DialogService>();
// Dynamically add ViewModels
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => !p.IsAbstract && typeof(PageBase).IsAssignableFrom(p));
foreach (var type in types)
builder.AddSingleton(typeof(PageBase), type);
builder.AddHttpClient();
var services = builder.BuildServiceProvider();
return services;
}
}

View File

@@ -0,0 +1,47 @@
using Needlework.Net.Desktop.ViewModels;
using Needlework.Net.Desktop.Views;
using SukiUI.Controls;
using System;
using System.Collections.Generic;
namespace Needlework.Net.Desktop.Services
{
public class DialogService
{
public IServiceProvider ServiceProvider { get; }
public Dictionary<string, SukiWindow> Dialogs { get; } = [];
public DialogService(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public void ShowEndpoint(string endpoint)
{
if (!Dialogs.TryGetValue(endpoint, out var _))
{
var dialog = new EndpointView();
dialog.DataContext = new EndpointViewModel(endpoint);
dialog.Show();
dialog.Closed += OnDialogClosed;
Dialogs[endpoint] = dialog;
}
}
private void OnDialogClosed(object? sender, EventArgs e)
{
if (sender == null)
return;
var dialog = (SukiWindow)sender;
if (dialog.DataContext is EndpointViewModel vm)
{
Dialogs.Remove(vm.Endpoint);
dialog.DataContext = null;
}
dialog.Closed -= OnDialogClosed;
}
}
}

View File

@@ -0,0 +1,31 @@
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using Needlework.Net.Core;
using System.Net.Http;
using System.Threading.Tasks;
namespace Needlework.Net.Desktop.Services
{
public partial class LcuService : ObservableObject
{
public HttpClient HttpClient { get; }
public LcuSchemaHandler LcuSchemaHandler { get; }
[ObservableProperty] private string _statusText = "Offline";
[ObservableProperty] private IBrush _statusColor = new SolidColorBrush(Colors.Red.ToUInt32());
[ObservableProperty] private string _statusAddress = "N/A";
public LcuService(HttpClient httpClient)
{
HttpClient = httpClient;
Task.Run(ProcessBackground);
}
private void ProcessBackground()
{
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Needlework.Net.Desktop
{
public class TextUpdatedEventArgs(string text) : EventArgs
{
public string Text { get; } = text;
}
}

View File

@@ -0,0 +1,35 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Needlework.Net.Desktop.ViewModels;
using System;
namespace Needlework.Net.Desktop
{
public class ViewLocator : IDataTemplate
{
public Control? Build(object? param)
{
if (param is null)
return new TextBlock { Text = "data was null" };
var name = param.GetType().FullName!.Replace("ViewModels", "Views")
.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
else
{
return new TextBlock { Text = "Not Found: " + name };
}
}
public bool Match(object? data)
{
if (data is PageBase) return true;
return false;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Needlework.Net.Desktop.ViewModels
{
public class AboutViewModel : PageBase
{
public AboutViewModel() : base("About", Material.Icons.MaterialIconKind.InfoCircle)
{
}
}
}

View File

@@ -0,0 +1,75 @@
using Avalonia.Collections;
using BlossomiShymae.GrrrLCU;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SukiUI.Controls;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class ConsoleViewModel : PageBase
{
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
[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? _responseAuthentication = null;
public event EventHandler<TextUpdatedEventArgs>? ResponseBodyUpdated;
public ConsoleViewModel() : base("Console", Material.Icons.MaterialIconKind.Console, -100)
{
}
[RelayCommand]
private async Task SendRequest()
{
try
{
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 = Connector.GetProcessInfo();
var response = await Connector.SendAsync(method, RequestPath) ?? throw new Exception("Response is null.");
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
var body = await response.Content.ReadAsStringAsync();
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
ResponseStatus = response.StatusCode.ToString();
ResponsePath = $"https://127.0.0.1/{processInfo.AppPort}{RequestPath}";
ResponseAuthentication = riotAuthentication.Value;
ResponseBodyUpdated?.Invoke(this, new(body));
});
}
catch (Exception ex)
{
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
ResponseStatus = null;
ResponsePath = null;
ResponseAuthentication = null;
ResponseBodyUpdated?.Invoke(this, new(string.Empty));
});
}
}
}
}

View File

@@ -0,0 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class EndpointViewModel(string endpoint) : ObservableObject
{
public string Endpoint { get; } = endpoint;
public string Title => $"Needlework.Net - {Endpoint}";
}
}

View File

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

View File

@@ -0,0 +1,55 @@
using Avalonia.Media;
using BlossomiShymae.GrrrLCU;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class HomeViewModel : PageBase
{
[ObservableProperty] private string _statusText = string.Empty;
[ObservableProperty] private IBrush? _statusForeground;
[ObservableProperty] private string _statusAddress = string.Empty;
public HomeViewModel() : base("Home", Material.Icons.MaterialIconKind.Home, int.MinValue)
{
Task.Run(async () => { while (true) { SetStatus(); await Task.Delay(TimeSpan.FromSeconds(5)); } });
}
private void SetStatus()
{
void Set(string text, Color color, string address)
{
StatusText = text;
StatusForeground = new SolidColorBrush(color.ToUInt32());
StatusAddress = address;
}
try
{
var processInfo = Connector.GetProcessInfo();
Avalonia.Threading.Dispatcher.UIThread.Post(() => Set("Online", Colors.Green, $"https://127.0.0.1:{processInfo.AppPort}/"));
}
catch (InvalidOperationException)
{
Avalonia.Threading.Dispatcher.UIThread.Post(() => Set("Offline", Colors.Red, "N/A"));
}
}
[RelayCommand]
private void OpenUrl(string url)
{
var process = new Process()
{
StartInfo = new ProcessStartInfo(url)
{
UseShellExecute = true
}
};
process.Start();
}
}
}

View File

@@ -0,0 +1,41 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace Needlework.Net.Desktop.ViewModels
{
public partial class MainWindowViewModel : ObservableObject
{
public IAvaloniaReadOnlyList<PageBase> Pages { get; }
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
public MainWindowViewModel(IEnumerable<PageBase> pages)
{
Pages = new AvaloniaList<PageBase>(pages.OrderBy(x => x.Index).ThenBy(x => x.DisplayName));
}
[RelayCommand]
private void OpenUrl(string url)
{
var process = new Process()
{
StartInfo = new ProcessStartInfo(url)
{
UseShellExecute = true
}
};
process.Start();
}
[RelayCommand]
private void OpenConsole()
{
}
}
}

View File

@@ -0,0 +1,13 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Material.Icons;
namespace Needlework.Net.Desktop.ViewModels
{
public abstract partial class PageBase(string displayName, MaterialIconKind icon, int index = 0) : ObservableValidator
{
[ObservableProperty] private string _displayName = displayName;
[ObservableProperty] private MaterialIconKind _icon = icon;
[ObservableProperty] private int _index = index;
}
}

View File

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

View File

@@ -0,0 +1,39 @@
<UserControl 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Needlework.Net.Desktop.Views.AboutView"
x:DataType="vm:AboutViewModel">
<ScrollViewer>
<WrapPanel Margin="8"
theme:WrapPanelExtensions.AnimatedScroll="true"
Orientation="Horizontal">
<suki:GlassCard Margin="8">
<Image Source="/Assets/about.png"
RenderOptions.BitmapInterpolationMode="MediumQuality"
Width="200"
Height="200"/>
</suki:GlassCard>
<StackPanel>
<suki:GlassCard Width="400" Margin="8">
<StackPanel>
<TextBlock Classes="h3">Blossomi Shymae</TextBlock>
</StackPanel>
</suki:GlassCard>
<suki:GlassCard Width="400" Margin="8">
<suki:GroupBox Header="About">
<TextBlock TextWrapping="Wrap">
Needlework.Net is the sister project of Needlework. Like Needlework, this project is inspired by
LCU Explorer. This tool was made to help others with LCU development. Feel free to ask any questions
or help contribute to the project! 💜
</TextBlock>
</suki:GroupBox>
</suki:GlassCard>
</StackPanel>
</WrapPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Needlework.Net.Desktop.Views;
public partial class AboutView : UserControl
{
public AboutView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,65 @@
<UserControl 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Needlework.Net.Desktop.Views.ConsoleView"
x:DataType="vm:ConsoleViewModel">
<Grid Margin="16" RowDefinitions="auto,*" ColumnDefinitions="*,*">
<Grid Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2">
<suki:GlassCard Margin="0 0 0 16">
<suki:GroupBox Header="Console">
<Grid RowDefinitions="auto,auto" ColumnDefinitions="auto,*">
<ComboBox ItemsSource="{Binding RequestMethods}" SelectedItem="{Binding RequestMethodSelected}"
Grid.Row="0" Grid.Column="0"/>
<TextBox Text="{Binding RequestPath}"
Grid.Row="0" Grid.Column="1"
Watermark="E.g. /lol-summoner/v1/current-summoner"/>
<TextBox Text="{Binding RequestBody}" Height="200" AcceptsReturn="True" TextWrapping="Wrap"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
</Grid>
</suki:GroupBox>
</suki:GlassCard>
<Button Classes="Flat Rounded"
Margin="0 0 0 0"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
FontWeight="DemiBold"
Command="{Binding SendRequestCommand}">
Send
</Button>
</Grid>
<StackPanel
Margin="0 0 8 0"
Grid.Row="1"
Grid.Column="0">
<suki:GlassCard Margin="0 4">
<TextBlock Text="{Binding ResponsePath}"/>
</suki:GlassCard>
<suki:GlassCard Margin="0 4">
<TextBlock Text="{Binding ResponseStatus}"/>
</suki:GlassCard>
<suki:GlassCard Margin="0 4">
<TextBlock Text="{Binding ResponseAuthentication}" />
</suki:GlassCard>
</StackPanel>
<suki:GlassCard
Margin="0 8"
Grid.Row="1"
Grid.Column="1">
<avaloniaEdit:TextEditor Name="ResponseEditor"
Text=""
FontFamily="Cascadia Code,Consolas,Menlo,Monospace"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Visible"
FontWeight="Light"
FontSize="14"/>
</suki:GlassCard>
</Grid>
</UserControl>

View File

@@ -0,0 +1,56 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Styling;
using AvaloniaEdit;
using AvaloniaEdit.Highlighting;
using AvaloniaEdit.Indentation.CSharp;
using AvaloniaEdit.TextMate;
using Needlework.Net.Desktop.ViewModels;
using SukiUI;
using System.Text.Json;
using TextMateSharp.Grammars;
namespace Needlework.Net.Desktop.Views;
public partial class ConsoleView : UserControl
{
private TextEditor? _responseEditor;
public ConsoleView()
{
InitializeComponent();
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
_responseEditor!.TextArea.IndentationStrategy = new CSharpIndentationStrategy(_responseEditor.Options);
_responseEditor!.TextArea.RightClickMovesCaret = true;
_responseEditor!.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("JavaScript");
((ConsoleViewModel)DataContext!)!.ResponseBodyUpdated += ConsoleView_ResponseBodyUpdated;
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
}
private void ConsoleView_ResponseBodyUpdated(object? sender, TextUpdatedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Text))
_responseEditor!.Text = JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(e.Text), App.JsonSerializerOptions);
else _responseEditor!.Text = e.Text;
}
private void OnBaseThemeChanged(ThemeVariant currentTheme)
{
var registryOptions = new RegistryOptions(
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
var textMateInstallation = _responseEditor.InstallTextMate(registryOptions);
textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
.GetLanguageByExtension(".json").Id));
}
}

View File

@@ -0,0 +1,19 @@
<Window 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Needlework.Net.Desktop.Views.EndpointView"
x:DataType="vm:EndpointViewModel"
Title="{Binding Title}"
Width="1280"
Height="720">
<Grid Margin="8" RowDefinitions="auto,*" ColumnDefinitions="*">
<TextBlock Classes="h3" Grid.Row="0" Grid.Column="0" Text="{Binding Endpoint}"/>
<ScrollViewer Grid.Row="1" Grid.Column="0">
</ScrollViewer>
</Grid>
</Window>

View File

@@ -0,0 +1,11 @@
using SukiUI.Controls;
namespace Needlework.Net.Desktop.Views;
public partial class EndpointView : SukiWindow
{
public EndpointView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,27 @@
<UserControl 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Needlework.Net.Desktop.Views.EndpointsView"
x:DataType="vm:EndpointsViewModel">
<!-- TOP LEVEL -->
<suki:BusyArea IsBusy="{Binding IsBusy}" BusyText="Loading...">
<Grid Margin="16" RowDefinitions="auto,auto,*" ColumnDefinitions="*">
<TextBlock Classes="h3" Margin="0 4" Grid.Row="0" Grid.Column="0">Endpoints</TextBlock>
<TextBox Watermark="Search" Margin="0 4" Text="{Binding Search}" Grid.Row="1" Grid.Column="0"/>
<ScrollViewer Grid.Row="2" Grid.Column="0">
<ListBox ItemsSource="{Binding Query}" SelectedItem="{Binding SelectedQuery}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="White" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</suki:BusyArea>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Needlework.Net.Desktop.Views
{
public partial class EndpointsView : UserControl
{
public EndpointsView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,75 @@
<UserControl 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Needlework.Net.Desktop.Views.HomeView"
x:DataType="vm:HomeViewModel">
<!-- TOP LEVEL -->
<ScrollViewer>
<WrapPanel Margin="8"
theme:WrapPanelExtensions.AnimatedScroll="true"
Orientation="Horizontal">
<!-- WELCOME -->
<StackPanel>
<suki:GlassCard Margin="8">
<StackPanel>
<TextBlock Classes="h3">Welcome to Needlework.Net</TextBlock>
<TextBlock>Get started with LCU development by clicking on the endpoints tab in the left panel.</TextBlock>
</StackPanel>
</suki:GlassCard>
<suki:GlassCard Margin="8" Classes="Accent">
<TextBlock TextWrapping="Wrap">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.</TextBlock>
</suki:GlassCard>
</StackPanel>
<!-- STATUS -->
<StackPanel>
<suki:GlassCard Margin="8" Width="250">
<suki:GroupBox Header="Status">
<TextBlock FontSize="24" FontWeight="Bold" Margin="0 4" Foreground="{Binding StatusForeground}" Text="{Binding StatusText}" />
</suki:GroupBox>
</suki:GlassCard>
<suki:GlassCard Margin="8" Width="250">
<suki:GroupBox Header="Address">
<TextBlock Text="{Binding StatusAddress}"/>
</suki:GroupBox>
</suki:GlassCard>
</StackPanel>
<!-- LEGAL -->
<suki:GlassCard Margin="8" Width="300">
<suki:GroupBox Header="Disclaimer">
<TextBlock TextWrapping="Wrap">Needlework.Net isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties. Riot Games, and all associated properties are trademarks or registered trademarks of Riot Games, Inc.</TextBlock>
</suki:GroupBox>
</suki:GlassCard>
<!-- FOOTER -->
<StackPanel>
<suki:GlassCard Margin="8" Width="400">
<StackPanel>
<TextBlock>© 2024 - Blossomi Shymae</TextBlock>
<TextBlock>MIT License</TextBlock>
</StackPanel>
</suki:GlassCard>
<suki:GlassCard Margin="8" Width="400">
<suki:GroupBox Header="Resources">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Command" Value="{Binding OpenUrlCommand}"/>
</Style>
</StackPanel.Styles>
<Button CommandParameter="https://hextechdocs.dev/tag/lcu/" Margin="0 0 16 0">
Hextech Docs
</Button>
<Button CommandParameter="https://hextechdocs.dev/getting-started-with-the-lcu-api/">
Getting Started
</Button>
</StackPanel>
</suki:GroupBox>
</suki:GlassCard>
</StackPanel>
</WrapPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Needlework.Net.Desktop.Views
{
public partial class HomeView : UserControl
{
public HomeView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,57 @@
<Window 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:i="https://github.com/projektanker/icons.avalonia"
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Needlework.Net.Desktop.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Title="Needlework.Net"
Width="1280"
Height="720">
<!-- TOP LEVEL -->
<suki:SukiSideMenu ItemsSource="{Binding Pages}">
<!-- ITEMS -->
<suki:SukiSideMenu.ItemTemplate>
<DataTemplate>
<suki:SukiSideMenuItem Header="{Binding DisplayName}">
<suki:SukiSideMenuItem.Icon>
<materialIcons:MaterialIcon Kind="{Binding Icon}" />
</suki:SukiSideMenuItem.Icon>
</suki:SukiSideMenuItem>
</DataTemplate>
</suki:SukiSideMenu.ItemTemplate>
<!-- FOOTER -->
<suki:SukiSideMenu.FooterContent>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Button.Basic">
<Setter Property="Command" Value="{Binding OpenUrlCommand}" />
</Style>
<Style Selector="materialIcons|MaterialIcon">
<Setter Property="Width" Value="25" />
<Setter Property="Height" Value="25" />
</Style>
<Style Selector="i|Icon">
<Setter Property="FontSize" Value="25" />
</Style>
</StackPanel.Styles>
<Button Classes="Flat"
Content="{Binding Version}" />
<Button Classes="Basic"
CommandParameter="https://github.com/BlossomiShymae/Needlework.Net"
ToolTip.Tip="Open on GitHub.">
<materialIcons:MaterialIcon Kind="Github" />
</Button>
<Button Classes="Basic"
CommandParameter="https://discord.gg/chEvEX5J4E"
ToolTip.Tip="Open Discord server.">
<i:Icon Value="fa-brand fa-discord" />
</Button>
</StackPanel>
</suki:SukiSideMenu.FooterContent>
</suki:SukiSideMenu>
</Window>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
using SukiUI.Controls;
namespace Needlework.Net.Desktop.Views;
public partial class MainWindow : SukiWindow
{
public MainWindow()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,20 @@
<UserControl 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Needlework.Net.Desktop.Views.PluginView"
x:DataType="vm:PluginViewModel">
<!-- TOP LEVEL -->
<ScrollViewer>
<StackPanel Margin="8">
<suki:GlassCard>
<StackPanel>
</StackPanel>
</suki:GlassCard>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Needlework.Net.Desktop.Views
{
public partial class PluginView : UserControl
{
public PluginView()
{
InitializeComponent();
}
}
}