diff --git a/Needlework.Net.Core.Tests/LcuSchemaHandlerTest.cs b/Needlework.Net.Core.Tests/LcuSchemaHandlerTest.cs new file mode 100644 index 0000000..62ab554 --- /dev/null +++ b/Needlework.Net.Core.Tests/LcuSchemaHandlerTest.cs @@ -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); + } +} \ No newline at end of file diff --git a/Needlework.Net.Core.Tests/ResourcesTest.cs b/Needlework.Net.Core.Tests/ResourcesTest.cs new file mode 100644 index 0000000..e0ceb0f --- /dev/null +++ b/Needlework.Net.Core.Tests/ResourcesTest.cs @@ -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"); + } +} \ No newline at end of file diff --git a/Needlework.Net.Core.Tests/UnitTest1.cs b/Needlework.Net.Core.Tests/UnitTest1.cs deleted file mode 100644 index 06c2cce..0000000 --- a/Needlework.Net.Core.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Needlework.Net.Core.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} \ No newline at end of file diff --git a/Needlework.Net.Core/Class1.cs b/Needlework.Net.Core/Class1.cs deleted file mode 100644 index 5aaa21b..0000000 --- a/Needlework.Net.Core/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Needlework.Net.Core; - -public class Class1 -{ - -} diff --git a/Needlework.Net.Core/LcuConnector.cs b/Needlework.Net.Core/LcuConnector.cs new file mode 100644 index 0000000..475c1b7 --- /dev/null +++ b/Needlework.Net.Core/LcuConnector.cs @@ -0,0 +1,10 @@ +using BlossomiShymae.GrrrLCU; + +namespace Needlework.Net.Core; + +public static class LcuConnector +{ + public static Func GetProcessInfo { get; } = Connector.GetProcessInfo; + public static Func GetLeagueClientUri { get; } = Connector.GetLeagueClientUri; + public static Func> SendAsync { get; } = Connector.SendAsync; +} \ No newline at end of file diff --git a/Needlework.Net.Core/LcuSchemaHandler.cs b/Needlework.Net.Core/LcuSchemaHandler.cs new file mode 100644 index 0000000..5025ed0 --- /dev/null +++ b/Needlework.Net.Core/LcuSchemaHandler.cs @@ -0,0 +1,49 @@ +using Microsoft.OpenApi.Models; + +namespace Needlework.Net.Core; + +public class LcuSchemaHandler +{ + internal OpenApiDocument OpenApiDocument { get; } + + public SortedDictionary 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; + } + } + } +} \ No newline at end of file diff --git a/Needlework.Net.Core/Needlework.Net.Core.csproj b/Needlework.Net.Core/Needlework.Net.Core.csproj index fa71b7a..c561f21 100644 --- a/Needlework.Net.Core/Needlework.Net.Core.csproj +++ b/Needlework.Net.Core/Needlework.Net.Core.csproj @@ -6,4 +6,10 @@ enable + + + + + + diff --git a/Needlework.Net.Core/Resources.cs b/Needlework.Net.Core/Resources.cs new file mode 100644 index 0000000..94922d1 --- /dev/null +++ b/Needlework.Net.Core/Resources.cs @@ -0,0 +1,21 @@ +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; + +namespace Needlework.Net.Core; + +public static class Resources +{ + /// + /// Get the OpenApi document of the LCU schema. Provided by dysolix. + /// + /// + /// + public static async Task 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; + } +} diff --git a/Needlework.Net.Desktop/App.axaml b/Needlework.Net.Desktop/App.axaml index 6ec7d0a..b7d474f 100644 --- a/Needlework.Net.Desktop/App.axaml +++ b/Needlework.Net.Desktop/App.axaml @@ -1,10 +1,19 @@ + 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"> - - - - + + + + + + + + + + \ No newline at end of file diff --git a/Needlework.Net.Desktop/App.axaml.cs b/Needlework.Net.Desktop/App.axaml.cs index 1186c08..932f95f 100644 --- a/Needlework.Net.Desktop/App.axaml.cs +++ b/Needlework.Net.Desktop/App.axaml.cs @@ -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() + }; } base.OnFrameworkInitializationCompleted(); diff --git a/Needlework.Net.Desktop/Assets/about.png b/Needlework.Net.Desktop/Assets/about.png new file mode 100644 index 0000000..4291c01 Binary files /dev/null and b/Needlework.Net.Desktop/Assets/about.png differ diff --git a/Needlework.Net.Desktop/MainWindow.axaml b/Needlework.Net.Desktop/MainWindow.axaml deleted file mode 100644 index 59b4807..0000000 --- a/Needlework.Net.Desktop/MainWindow.axaml +++ /dev/null @@ -1,9 +0,0 @@ - - Welcome to Avalonia! - diff --git a/Needlework.Net.Desktop/MainWindow.axaml.cs b/Needlework.Net.Desktop/MainWindow.axaml.cs deleted file mode 100644 index 609fceb..0000000 --- a/Needlework.Net.Desktop/MainWindow.axaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.Controls; - -namespace Needlework.Net.Desktop; - -public partial class MainWindow : Window -{ - public MainWindow() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/Needlework.Net.Desktop/Needlework.Net.Desktop.csproj b/Needlework.Net.Desktop/Needlework.Net.Desktop.csproj index f3618b7..b0bd5f6 100644 --- a/Needlework.Net.Desktop/Needlework.Net.Desktop.csproj +++ b/Needlework.Net.Desktop/Needlework.Net.Desktop.csproj @@ -1,23 +1,48 @@  WinExe - net8.0 + net8.0-windows10.0.17763.0 enable true app.manifest true + + False + 10.0.17763.0 + 0.1.0.0 + 0.1.0.0 + False + - - - - + + + + - + + + + + + + + + + + + + + + + + + + diff --git a/Needlework.Net.Desktop/Program.cs b/Needlework.Net.Desktop/Program.cs index 0bf9f5e..2dba233 100644 --- a/Needlework.Net.Desktop/Program.cs +++ b/Needlework.Net.Desktop/Program.cs @@ -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() - .UsePlatformDetect() - .WithInterFont() - .LogToTrace(); + { + IconProvider.Current + .Register(); + + return AppBuilder.Configure(() => new App(BuildServices())) + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); + } + + private static IServiceProvider BuildServices() + { + var builder = new ServiceCollection(); + + builder.AddSingleton(); + builder.AddSingleton(); + // 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; + } } diff --git a/Needlework.Net.Desktop/Services/DialogService.cs b/Needlework.Net.Desktop/Services/DialogService.cs new file mode 100644 index 0000000..b8f68e4 --- /dev/null +++ b/Needlework.Net.Desktop/Services/DialogService.cs @@ -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 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; + } + } +} diff --git a/Needlework.Net.Desktop/Services/LcuService.cs b/Needlework.Net.Desktop/Services/LcuService.cs new file mode 100644 index 0000000..aa1acd3 --- /dev/null +++ b/Needlework.Net.Desktop/Services/LcuService.cs @@ -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() + { + + } + } +} diff --git a/Needlework.Net.Desktop/TextUpdatedEventArgs.cs b/Needlework.Net.Desktop/TextUpdatedEventArgs.cs new file mode 100644 index 0000000..246efe2 --- /dev/null +++ b/Needlework.Net.Desktop/TextUpdatedEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace Needlework.Net.Desktop +{ + public class TextUpdatedEventArgs(string text) : EventArgs + { + public string Text { get; } = text; + } +} diff --git a/Needlework.Net.Desktop/ViewLocator.cs b/Needlework.Net.Desktop/ViewLocator.cs new file mode 100644 index 0000000..3f8142b --- /dev/null +++ b/Needlework.Net.Desktop/ViewLocator.cs @@ -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; + } + } +} diff --git a/Needlework.Net.Desktop/ViewModels/AboutViewModel.cs b/Needlework.Net.Desktop/ViewModels/AboutViewModel.cs new file mode 100644 index 0000000..26d7951 --- /dev/null +++ b/Needlework.Net.Desktop/ViewModels/AboutViewModel.cs @@ -0,0 +1,9 @@ +namespace Needlework.Net.Desktop.ViewModels +{ + public class AboutViewModel : PageBase + { + public AboutViewModel() : base("About", Material.Icons.MaterialIconKind.InfoCircle) + { + } + } +} diff --git a/Needlework.Net.Desktop/ViewModels/ConsoleViewModel.cs b/Needlework.Net.Desktop/ViewModels/ConsoleViewModel.cs new file mode 100644 index 0000000..4226126 --- /dev/null +++ b/Needlework.Net.Desktop/ViewModels/ConsoleViewModel.cs @@ -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 RequestMethods { get; } = new AvaloniaList(["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? 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)); + }); + } + } + } +} diff --git a/Needlework.Net.Desktop/ViewModels/EndpointViewModel.cs b/Needlework.Net.Desktop/ViewModels/EndpointViewModel.cs new file mode 100644 index 0000000..608fcfa --- /dev/null +++ b/Needlework.Net.Desktop/ViewModels/EndpointViewModel.cs @@ -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}"; + } +} diff --git a/Needlework.Net.Desktop/ViewModels/EndpointsViewModel.cs b/Needlework.Net.Desktop/ViewModels/EndpointsViewModel.cs new file mode 100644 index 0000000..a2ad6e1 --- /dev/null +++ b/Needlework.Net.Desktop/ViewModels/EndpointsViewModel.cs @@ -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 _plugins = []; + [ObservableProperty] private bool _isBusy = true; + [ObservableProperty] private string _search = string.Empty; + [ObservableProperty] private List _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); + } + } +} diff --git a/Needlework.Net.Desktop/ViewModels/HomeViewModel.cs b/Needlework.Net.Desktop/ViewModels/HomeViewModel.cs new file mode 100644 index 0000000..c46dbba --- /dev/null +++ b/Needlework.Net.Desktop/ViewModels/HomeViewModel.cs @@ -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(); + } + } +} diff --git a/Needlework.Net.Desktop/ViewModels/MainWindowViewModel.cs b/Needlework.Net.Desktop/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..71dd498 --- /dev/null +++ b/Needlework.Net.Desktop/ViewModels/MainWindowViewModel.cs @@ -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 Pages { get; } + + public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0"; + + public MainWindowViewModel(IEnumerable pages) + { + Pages = new AvaloniaList(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() + { + + } + } +} diff --git a/Needlework.Net.Desktop/ViewModels/PageBase.cs b/Needlework.Net.Desktop/ViewModels/PageBase.cs new file mode 100644 index 0000000..5e0e9f0 --- /dev/null +++ b/Needlework.Net.Desktop/ViewModels/PageBase.cs @@ -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; + } +} \ No newline at end of file diff --git a/Needlework.Net.Desktop/ViewModels/PluginViewModel.cs b/Needlework.Net.Desktop/ViewModels/PluginViewModel.cs new file mode 100644 index 0000000..29d759e --- /dev/null +++ b/Needlework.Net.Desktop/ViewModels/PluginViewModel.cs @@ -0,0 +1,7 @@ +namespace Needlework.Net.Desktop.ViewModels +{ + public class PluginViewModel + { + public PluginViewModel() { } + } +} diff --git a/Needlework.Net.Desktop/Views/AboutView.axaml b/Needlework.Net.Desktop/Views/AboutView.axaml new file mode 100644 index 0000000..67d9051 --- /dev/null +++ b/Needlework.Net.Desktop/Views/AboutView.axaml @@ -0,0 +1,39 @@ + + + + + + + + + + Blossomi Shymae + + + + + + 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! 💜 + + + + + + + diff --git a/Needlework.Net.Desktop/Views/AboutView.axaml.cs b/Needlework.Net.Desktop/Views/AboutView.axaml.cs new file mode 100644 index 0000000..6b9dfa0 --- /dev/null +++ b/Needlework.Net.Desktop/Views/AboutView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Desktop.Views; + +public partial class AboutView : UserControl +{ + public AboutView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Needlework.Net.Desktop/Views/ConsoleView.axaml b/Needlework.Net.Desktop/Views/ConsoleView.axaml new file mode 100644 index 0000000..4739d6e --- /dev/null +++ b/Needlework.Net.Desktop/Views/ConsoleView.axaml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Needlework.Net.Desktop/Views/ConsoleView.axaml.cs b/Needlework.Net.Desktop/Views/ConsoleView.axaml.cs new file mode 100644 index 0000000..72856ff --- /dev/null +++ b/Needlework.Net.Desktop/Views/ConsoleView.axaml.cs @@ -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("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(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)); + } +} \ No newline at end of file diff --git a/Needlework.Net.Desktop/Views/EndpointView.axaml b/Needlework.Net.Desktop/Views/EndpointView.axaml new file mode 100644 index 0000000..15038c8 --- /dev/null +++ b/Needlework.Net.Desktop/Views/EndpointView.axaml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/Needlework.Net.Desktop/Views/EndpointView.axaml.cs b/Needlework.Net.Desktop/Views/EndpointView.axaml.cs new file mode 100644 index 0000000..b5d43f3 --- /dev/null +++ b/Needlework.Net.Desktop/Views/EndpointView.axaml.cs @@ -0,0 +1,11 @@ +using SukiUI.Controls; + +namespace Needlework.Net.Desktop.Views; + +public partial class EndpointView : SukiWindow +{ + public EndpointView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Needlework.Net.Desktop/Views/EndpointsView.axaml b/Needlework.Net.Desktop/Views/EndpointsView.axaml new file mode 100644 index 0000000..566ed40 --- /dev/null +++ b/Needlework.Net.Desktop/Views/EndpointsView.axaml @@ -0,0 +1,27 @@ + + + + + Endpoints + + + + + + + + + + + + + \ No newline at end of file diff --git a/Needlework.Net.Desktop/Views/EndpointsView.axaml.cs b/Needlework.Net.Desktop/Views/EndpointsView.axaml.cs new file mode 100644 index 0000000..9e2e5a0 --- /dev/null +++ b/Needlework.Net.Desktop/Views/EndpointsView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Desktop.Views +{ + public partial class EndpointsView : UserControl + { + public EndpointsView() + { + InitializeComponent(); + } + } +} diff --git a/Needlework.Net.Desktop/Views/HomeView.axaml b/Needlework.Net.Desktop/Views/HomeView.axaml new file mode 100644 index 0000000..b3298d4 --- /dev/null +++ b/Needlework.Net.Desktop/Views/HomeView.axaml @@ -0,0 +1,75 @@ + + + + + + + + + Welcome to Needlework.Net + Get started with LCU development by clicking on the endpoints tab in the left panel. + + + + 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. + + + + + + + + + + + + + + + + + + + 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. + + + + + + + © 2024 - Blossomi Shymae + MIT License + + + + + + + + + + + + + + + + + diff --git a/Needlework.Net.Desktop/Views/HomeView.axaml.cs b/Needlework.Net.Desktop/Views/HomeView.axaml.cs new file mode 100644 index 0000000..b3be36f --- /dev/null +++ b/Needlework.Net.Desktop/Views/HomeView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Desktop.Views +{ + public partial class HomeView : UserControl + { + public HomeView() + { + InitializeComponent(); + } + } +} diff --git a/Needlework.Net.Desktop/Views/MainWindow.axaml b/Needlework.Net.Desktop/Views/MainWindow.axaml new file mode 100644 index 0000000..3e41bfb --- /dev/null +++ b/Needlework.Net.Desktop/Views/MainWindow.axaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Needlework.Net.Desktop/Views/MainWindow.axaml.cs b/Needlework.Net.Desktop/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..35791fd --- /dev/null +++ b/Needlework.Net.Desktop/Views/MainWindow.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; +using SukiUI.Controls; + +namespace Needlework.Net.Desktop.Views; + +public partial class MainWindow : SukiWindow +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Needlework.Net.Desktop/Views/PluginView.axaml b/Needlework.Net.Desktop/Views/PluginView.axaml new file mode 100644 index 0000000..39831bb --- /dev/null +++ b/Needlework.Net.Desktop/Views/PluginView.axaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/Needlework.Net.Desktop/Views/PluginView.axaml.cs b/Needlework.Net.Desktop/Views/PluginView.axaml.cs new file mode 100644 index 0000000..0f1f41b --- /dev/null +++ b/Needlework.Net.Desktop/Views/PluginView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Desktop.Views +{ + public partial class PluginView : UserControl + { + public PluginView() + { + InitializeComponent(); + } + } +}