diff --git a/Needlework.Net/App.axaml b/Needlework.Net/App.axaml index a0e5438..f4259fe 100644 --- a/Needlework.Net/App.axaml +++ b/Needlework.Net/App.axaml @@ -6,11 +6,7 @@ xmlns:sty="using:FluentAvalonia.Styling" xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" RequestedThemeVariant="Dark"> - - - - - + @@ -21,7 +17,7 @@ - - + + \ No newline at end of file diff --git a/Needlework.Net/App.axaml.cs b/Needlework.Net/App.axaml.cs index a1bee0d..ea4277d 100644 --- a/Needlework.Net/App.axaml.cs +++ b/Needlework.Net/App.axaml.cs @@ -1,18 +1,33 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Controls.Templates; using Avalonia.Markup.Xaml; using Microsoft.Extensions.DependencyInjection; +using Needlework.Net.Extensions; using Needlework.Net.ViewModels.MainWindow; +using Needlework.Net.ViewModels.Pages; using Needlework.Net.Views.MainWindow; using System; +using System.Reflection; using System.Text.Json; +using System.Threading.Tasks; namespace Needlework.Net; -public partial class App(IServiceProvider serviceProvider) : Application +public partial class App : Application, IEnableLogger { - private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly IServiceProvider _serviceProvider; + + public App(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + + this.Log() + .Debug("NeedleworkDotNet version: {Version}", Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0"); + this.Log() + .Debug("OS description: {Description}", System.Runtime.InteropServices.RuntimeInformation.OSDescription); + } public static JsonSerializerOptions JsonSerializerOptions { get; } = new() { @@ -26,11 +41,17 @@ public partial class App(IServiceProvider serviceProvider) : Application public override void Initialize() { + DataTemplates.Add(_serviceProvider.GetRequiredService()); AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { + foreach (var page in _serviceProvider.GetServices()) + { + Task.Run(page.InitializeAsync); + } + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.MainWindow = new MainWindowView() diff --git a/Needlework.Net/Assets/libraries.json b/Needlework.Net/Assets/libraries.json index ea81f39..7043e1c 100644 --- a/Needlework.Net/Assets/libraries.json +++ b/Needlework.Net/Assets/libraries.json @@ -1 +1,1796 @@ -[{"Repo":"GrrrLCU","Description":"A simple wrapper for the LCU. Grrr. x3","Language":"C#","Link":"https://github.com/BlossomiShymae/GrrrLCU"},{"Repo":"Kunc.RiotGames","Description":null,"Language":"C#","Link":"https://github.com/AoshiW/Kunc.RiotGames"},{"Repo":"rito","Description":"Rito is a simple, crossplatform (Windows and Linux) C++20 library interfacing with Riot services (i.e. Riot REST API and League of Legends client).","Language":"cpp","Link":"https://github.com/bartekprtc/rito"},{"Repo":"R4J","Description":"A Java library containing the API for every Riot game","Language":"Java","Link":"https://github.com/stelar7/R4J"},{"Repo":"hasagi-core","Description":"LCU library with auto-generated types for request parameters and responses","Language":"JavaScript","Link":"https://github.com/dysolix/hasagi-core"},{"Repo":"lcu-driver","Description":"Python3 helper for the League of Legends LCU API.","Language":"Python","Link":"https://github.com/sousa-andre/lcu-driver"},{"Repo":"willump","Description":"Python3 helper for the League of Legends LCU API.","Language":"Python","Link":"https://github.com/elliejs/Willump"},{"Repo":"Irelia","Description":"LoL LCU Wrapper for Rust, built on top of hyper!","Language":"Rust","Link":"https://github.com/AlsoSylv/Irelia"},{"Repo":"Shaco","Description":"League of Legends LCU wrapper for rust","Language":"Rust","Link":"https://github.com/Leastrio/Shaco"},{"Repo":"hasagi-core","Description":"LCU library with auto-generated types for request parameters and responses","Language":"TypeScript","Link":"https://github.com/dysolix/hasagi-core"},{"Repo":"hexgate","Description":"LCU API wrapper for League of Legends","Language":"TypeScript","Link":"https://github.com/cuppachino/hexgate"}] \ No newline at end of file +[ + { + "owner": "BlossomiShymae", + "repo": "Briar", + "language": "C#", + "description": "A simple wrapper for the League Client and Game Client APIs.", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages/BlossomiShymae.Briar/" + }, + { + "name": "Documentation", + "url": "https://github.com/BlossomiShymae/Briar/blob/main/README.md#Usage" + } + ], + "metadata": { + "async": true + }, + "tags": [ + "lcu", + "rest", + "websocket", + "ws", + "ingame" + ] + }, + { + "owner": "MingweiSamuel", + "repo": "Camille", + "language": "C#", + "description": "Fully rate limited, automatic retrying, thread-safe. Automatic nightly releases.", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages/Camille.RiotGames/" + } + ], + "metadata": { + "v3": true, + "v4": true, + "async": true, + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true + } + }, + "tags": [ + "v3", + "v4", + "rate-limiting", + "lol", + "tft", + "lor", + "val" + ] + }, + { + "owner": "ercand", + "repo": "CottontailSummoners", + "description": "LeagueOfLegends v3 API wrapper. Website in asp.net MVC like www.op.gg", + "language": "C#", + "links": [ + { + "name": "NuGet", + "url": "https://github.com/ercand/CottontailSummoners/" + } + ], + "metadata": { + "v3": true, + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true + } + }, + "tags": [ + "v3", + "rate-limiter" + ] + }, + { + "owner": "golf1052", + "repo": "CreepScore", + "language": "C#", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages/Creep.Score/" + } + ], + "metadata": {} + }, + { + "owner": "AoshiW", + "repo": "Kunc.RiotGames", + "language": "C#", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages?q=Kunc.RiotGames." + } + ], + "metadata": { + "slow": true, + "bugFree": false, + "otherVeryUsefulMetadata": null + }, + "tags": [ + "lol", + "lcu", + "ingame", + "tft", + "lor", + "rate-limiting", + "v4" + ] + }, + { + "owner": "XeeX", + "repo": "LeagueOfLegendsAPI", + "language": "C#", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages/Remake.PortableLeagueAPI/" + } + ], + "metadata": {} + }, + { + "owner": "p-ob", + "repo": "LolSharp", + "language": "C#", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages/LolSharp/" + } + ], + "metadata": {} + }, + { + "owner": "sdesyllas", + "repo": "RiotApi.NET", + "language": "C#", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages/RiotApi.NET/" + } + ], + "metadata": {} + }, + { + "owner": "Msx752", + "repo": "RiotGamesAPI", + "description": "Riot Games API Library v3 (all in one)", + "language": "C#", + "links": [ + { + "name": "AspNetCore", + "url": "https://www.nuget.org/packages/RiotGamesAPI/" + }, + { + "name": "NetStandard", + "url": "https://www.nuget.org/packages/RiotGamesAPI/" + }, + { + "name": "AspNet", + "url": "https://www.nuget.org/packages/RiotGamesAPI/" + }, + { + "name": "Documentation", + "url": "https://riotgamesapi.readme.io/docs" + } + ], + "metadata": { + "v3": true, + "async": true, + "caching": { + "static-api": true, + "non-static-api": true + }, + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true + } + }, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "aj-r", + "repo": "RiotNet", + "language": "C#", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages/RiotNet/" + }, + { + "name": "Documentation", + "url": "http://aj-r.github.io/RiotNet/docs/interface_riot_net_1_1_i_riot_client.html" + } + ], + "metadata": { + "v3": true, + "async": true, + "caching": { + "static-api": false, + "non-static-api": false + }, + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true + } + }, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "BenFradet", + "repo": "RiotSharp", + "language": "C#", + "description": "RiotSharp's ASP.NET Core integration. **NOTE: Library is not actively maintained! You will have to fork the project and add missing enum values, classes and properties.**", + "links": [], + "metadata": { + "v4": true, + "async": true, + "caching": false, + "rate-limiter": { + "header-driven": false, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true + } + }, + "tags": [ + "v4", + "rate-limiting", + "asp-net-core" + ] + }, + { + "owner": "ChristianFreak", + "repo": "RiotWrapper.NET", + "description": "An asynchronus .NET wrapper for the League of Legends API", + "language": "C#", + "links": [ + { + "name": "NuGet", + "url": "https://www.nuget.org/packages/RiotWrapper.NET/" + } + ], + "metadata": {}, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "Dan-Tan", + "repo": "riot-cpp", + "language": "cpp", + "description": "A c++ library implementing allowing basic access to riot's api using libcurl and jsoncpp", + "links": [ + { + "name": "Documentation", + "url": "https://github.com/Dan-Tan/riot-cpp" + } + ], + "metadata": {}, + "tags": [ + "v4", + "rate-limiting", + "logging" + ] + }, + { + "owner": "bartekprtc", + "repo": "rito", + "description": "Rito is a simple, crossplatform (Windows and Linux) C++20 library interfacing with Riot services (i.e. Riot REST API and League of Legends client).", + "language": "cpp", + "links": [ + { + "name": "Docs", + "url": "https://bartekjaszczak.github.io/rito/" + } + ], + "metadata": { + "version": "0.1.0" + }, + "tags": [ + "lol", + "lcu", + "ingame", + "v4" + ] + }, + { + "owner": "aguxez", + "repo": "godfist", + "description": "Library for League of Legends API in Elixir.", + "language": "Elixir", + "links": [ + { + "name": "Documentation", + "url": "https://hexdocs.pm/godfist/Godfist.html" + } + ], + "metadata": { + "v3-supported": true + }, + "tags": [ + "v3" + ] + }, + { + "owner": "trilleplay", + "repo": "seraphine", + "description": "Seraphine is an API library written in Elixir for the Riot Games API.", + "language": "Elixir", + "links": [ + { + "name": "Documentation", + "url": "https://hexdocs.pm/seraphine/readme.html" + } + ], + "metadata": { + "version": "0.1.1" + }, + "tags": [ + "v4", + "rate-limiting" + ] + }, + { + "owner": "KnutZuidema", + "repo": "golio", + "language": "Go", + "links": [ + { + "name": "src", + "url": "https://github.com/KnutZuidema/golio" + }, + { + "name": "docs", + "url": "https://pkg.go.dev/github.com/KnutZuidema/golio" + } + ], + "metadata": { + "version": "1.0.0" + }, + "tags": [ + "v4", + "rate-limiting", + "caching", + "lol", + "lor", + "val" + ] + }, + { + "owner": "bbqtd", + "repo": "rg-kit", + "description": "A Riot Games developer toolkit", + "language": "Go", + "links": [ + { + "name": "src", + "url": "https://github.com/bbqtd/rg-kit" + }, + { + "name": "Documentation", + "url": "https://godoc.org/github.com/bbqtd/rg-kit" + } + ], + "metadata": {}, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "junioryono", + "repo": "Riot-API-Golang", + "language": "Go", + "links": [ + { + "name": "github", + "url": "https://github.com/junioryono/Riot-API-Golang" + } + ], + "metadata": { + "version": "0.1" + }, + "tags": [ + "v4", + "v5", + "ddragon", + "rate-limiting" + ] + }, + { + "owner": "yuhanfang", + "repo": "riot", + "description": "Go library for the Riot Games API", + "language": "Go", + "links": [ + { + "name": "Documentation", + "url": "https://github.com/yuhanfang/riot" + } + ], + "metadata": { + "version": "1.0" + }, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "meraki-analytics", + "repo": "orianna", + "description": "A highly configurable, usability-focused Riot API framework that takes care of all the details for you so you can focus on building your application", + "language": "Java", + "links": [ + { + "name": "Maven", + "url": "https://search.maven.org/search?q=g:com.merakianalytics.orianna" + }, + { + "name": "Documentation", + "url": "http://orianna.readthedocs.org/en/latest/" + }, + { + "name": "JavaDoc", + "url": "http://javadoc.io/doc/com.merakianalytics.orianna/orianna" + } + ], + "metadata": {}, + "tags": [ + "v4", + "rate-limiting", + "caching" + ] + }, + { + "owner": "stelar7", + "repo": "R4J", + "language": "Java", + "description": "A Java library containing the API for every Riot game", + "links": [], + "metadata": {}, + "tags": [ + "v4", + "v5", + "lcu", + "ingame", + "lol", + "tft", + "lor", + "val" + ] + }, + { + "owner": "shyos", + "repo": "Shyvana", + "language": "Java", + "links": [], + "metadata": {} + }, + { + "owner": "a64adam", + "repo": "ulti", + "language": "Java", + "links": [], + "metadata": {} + }, + { + "owner": "MingweiSamuel", + "repo": "Zyra", + "language": "Java", + "links": [ + { + "name": "Maven", + "url": "https://search.maven.org/search?q=g:com.mingweisamuel.zyra%20AND%20a:zyra" + }, + { + "name": "Nightlies", + "url": "http://www.mingweisamuel.com/Zyra/" + }, + { + "name": "Javadoc", + "url": "http://www.mingweisamuel.com/Zyra/apidocs/" + } + ], + "metadata": { + "async": true, + "rate-limiter": true, + "retries": true + }, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "fightmegg", + "repo": "riot-api", + "language": "JavaScript", + "description": "Fully featured Riot API client", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/@fightmegg/riot-api" + } + ], + "metadata": { + "v4": true, + "async": true, + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true, + "spread": true + }, + "cacher": { + "in-memory": true, + "redis": true + } + }, + "tags": [ + "v4", + "rate-limiting", + "caching" + ] + }, + { + "owner": "bcho04", + "repo": "galeforce", + "language": "JavaScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/galeforce" + }, + { + "name": "github", + "url": "https://github.com/bcho04/galeforce" + }, + { + "name": "docs", + "url": "https://bcho04.github.io/galeforce/" + } + ], + "metadata": { + "version": "0.5.0" + }, + "tags": [ + "v4", + "v5", + "rate-limiting", + "lol", + "lor", + "val", + "tft", + "cache" + ] + }, + { + "owner": "dysolix", + "repo": "hasagi-core", + "description": "LCU library with auto-generated types for request parameters and responses", + "language": "JavaScript", + "links": [ + { + "name": "GitHub", + "url": "https://github.com/dysolix/hasagi-core" + }, + { + "name": "Docs", + "url": "https://github.com/dysolix/hasagi-core/wiki" + }, + { + "name": "Extended Version GitHub", + "url": "https://github.com/dysolix/hasagi-extended" + } + ], + "metadata": {}, + "tags": [ + "lcu" + ] + }, + { + "owner": "perezpaya", + "repo": "Irelia", + "language": "Javascript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/irelia" + } + ], + "metadata": {} + }, + { + "owner": "LionelBergen", + "repo": "MundoScript", + "language": "JavaScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/leagueapiwrapper" + }, + { + "name": "Documentation", + "url": "https://github.com/LionelBergen/MundoScript" + } + ], + "metadata": {}, + "tags": [ + "v4" + ] + }, + { + "owner": "claudiowilson", + "repo": "LeagueJS", + "language": "JavaScript", + "links": [], + "metadata": {} + }, + { + "owner": "Colorfulstan", + "repo": "LeagueJS", + "language": "JavaScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/leaguejs" + }, + { + "name": "gitter", + "url": "https://gitter.im/League-JS/" + } + ], + "metadata": { + "version": "2.0.1", + "unit-tested": true, + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true, + "burst": true, + "spread": true + }, + "cacher": { + "in-memory": true, + "redis": false, + "db": false + } + }, + "tags": [ + "v4", + "caching", + "ddragon", + "rate-limiting" + ] + }, + { + "owner": "danielsogl", + "repo": "lol-stats-api", + "description": "A ready to use, configurable League of Legends API", + "language": "JavaScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/lol-stats-api" + }, + { + "name": "Documentation", + "url": "https://github.com/danielsogl/lol-stats-api" + } + ], + "metadata": { + "version": "1.0.0", + "rate-limiter": { + "per-region": false, + "burst": true, + "spread": true, + "method-rate-limiting": true + }, + "cacher": { + "in-memory": true, + "redis": true + } + }, + "tags": [ + "v3", + "rate-limiting", + "caching" + ] + }, + { + "owner": "emmorts", + "repo": "lolapi", + "language": "Javascript", + "links": [], + "metadata": {} + }, + { + "owner": "jwalton", + "repo": "lol-js", + "language": "Javascript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/lol-js" + } + ], + "metadata": {} + }, + { + "owner": "neamar", + "repo": "riot-lol-api", + "description": "A library tuned to handle with very high volumes of requests to the API (over 1k requests per seconds) over one or many instances", + "language": "JavaScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/riot-lol-api" + } + ], + "metadata": { + "version": "4.2.2", + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true, + "burst": true + }, + "cacher": { + "customizeable": true + } + }, + "tags": [ + "v3", + "rate-limiting", + "burst", + "high-usage" + ] + }, + { + "owner": "Colorfulstan", + "repo": "RiotRateLimiter-node", + "language": "JavaScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/riot-ratelimiter" + } + ], + "metadata": { + "version": "0.0.7", + "unit-tested": true + }, + "tags": [ + "v3", + "rate-limiting", + "header-driven", + "burst", + "spread", + "per-region" + ] + }, + { + "owner": "pinddfull", + "repo": "RiotAPI.grab", + "language": "TypeScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/riotapi-grab" + }, + { + "name": "github", + "url": "https://github.com/pinddFull/RiotAPI.grab" + } + ], + "metadata": { + "version": "0.0.3" + }, + "tags": [ + "v3", + "rate-limiter" + ] + }, + { + "owner": "brucewsinc", + "repo": "rito-pls", + "language": "Javascript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/rito-pls" + } + ], + "metadata": {} + }, + { + "owner": "TheDrone7", + "repo": "shieldbow", + "description": "A super easy-to-use RIOT API Wrapper with full type support.", + "language": "JavaScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/shieldbow" + }, + { + "name": "Documentation", + "url": "https://thedrone7.github.io/shieldbow/" + } + ], + "metadata": { + "version": "1.2.0" + }, + "tags": [ + "v4", + "v5", + "rate-limiting", + "caching", + "lol", + "ts" + ] + }, + { + "owner": "MingweiSamuel", + "repo": "TeemoJS", + "language": "JavaScript", + "description": "Fast & tiny, automatic retries & smart rate limiting, V4 & champion.gg support, all in 300 lines.", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/teemojs" + } + ], + "metadata": { + "v3": true, + "v4": true, + "async": true, + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true + } + }, + "tags": [ + "v3", + "v4", + "rate-limiting" + ] + }, + { + "owner": "Sansossio", + "repo": "twisted", + "language": "JavaScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/twisted" + }, + { + "name": "github", + "url": "https://github.com/Sansossio/twisted" + }, + { + "name": "examples", + "url": "https://github.com/Sansossio/twisted/tree/master/example" + } + ], + "metadata": { + "version": "2.0.0" + }, + "tags": [ + "rate-limiting", + "v4", + "lol", + "tft", + "caching" + ] + }, + { + "owner": "wookay", + "repo": "LOLTools.jl", + "language": "Julia", + "description": "Julia package to the Riot Games API for League of Legends.", + "links": [], + "metadata": { + "v3": false, + "v4": true, + "tft": false + }, + "tags": [ + "v4" + ] + }, + { + "owner": "stelar7", + "repo": "kotmaw", + "language": "Kotlin", + "description": "API Wrapper for kotlin", + "links": [], + "metadata": {}, + "tags": [ + "v3", + "rate-limiting", + "async", + "android" + ] + }, + { + "owner": "nspu", + "repo": "riot-api-android", + "description": "A library to use riot api and datadragon with android. It uses Retrofit and Picasso.", + "language": "Kotlin", + "links": [ + { + "name": "Github", + "url": "https://github.com/nspu/riot-api-android" + }, + { + "name": "Documentation", + "url": "https://nspu.github.io/riot-api-android/docs/index.html" + }, + { + "name": "Library (.aar)", + "url": "https://github.com/nspu/riot-api-android/releases" + } + ], + "metadata": { + "version": "0.0.1" + }, + "tags": [ + "v3", + "datadragon", + "android", + "caching" + ] + }, + { + "owner": "victhebeast", + "repo": "iOS-LoL-API", + "language": "Objective-C", + "links": [], + "metadata": {} + }, + { + "owner": "troystump", + "repo": "LoLAPI", + "language": "Objective-C", + "links": [], + "metadata": {} + }, + { + "owner": "arcanez", + "repo": "www-riotgames-leagueoflegends", + "description": "Perl wrapper around the Riot Games League of Legends API", + "language": "Perl", + "links": [ + { + "name": "github", + "url": "https://github.com/arcanez/www-riotgames-leagueoflegends" + }, + { + "name": "CPAN", + "url": "https://metacpan.org/pod/WWW::RiotGames::LeagueOfLegends" + } + ], + "metadata": { + "v3-supported": true, + "author": { + "name": "Justin Hunter", + "website": "http://warpedreality.org", + "twitter": "justindhunter", + "gitHub": "arcanez" + }, + "version": "0.0001" + } + }, + { + "owner": "rennokki", + "repo": "league-api", + "description": "League API is a PHP wrapper of RIOT Games' League of Legends API. This wrapper gives you access to both REST API and Data Dragon in an eloquent way that you'll love!", + "language": "PHP", + "links": [], + "metadata": { + "version": "1.0.3" + }, + "tags": [ + "v3" + ] + }, + { + "owner": "haringsrob", + "repo": "League-of-legends-php-class", + "language": "PHP", + "links": [], + "metadata": {} + }, + { + "owner": "m1so", + "repo": "LeagueWrap", + "description": "League of Legend API wrapper for PHP 5.6 and 7+. Supports PSR-6 compatible caching backends and rate-limiting, as well as batch requests.", + "language": "PHP", + "links": [ + { + "name": "Github", + "url": "https://github.com/m1so/LeagueWrap" + }, + { + "name": "Packagist", + "url": "https://packagist.org/packages/m1so/leaguewrap" + } + ], + "metadata": { + "version": "1.0" + }, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "Babacooll", + "repo": "lol-api", + "language": "PHP", + "links": [], + "metadata": {} + }, + { + "owner": "optii", + "repo": "LolApiBundle", + "language": "PHP", + "links": [], + "metadata": {} + }, + { + "owner": "DarkIntaqt", + "repo": "malPHPhite", + "description": "MalPHPhite is a beginner-friendly PHP Wrapper for the League of Legends API. Just include a single file and you are ready to go!", + "language": "PHP", + "links": [ + { + "name": "Tutorial", + "url": "https://darkintaqt.com/blog/malphphite" + } + ], + "tags": [ + "v4", + "v5", + "caching", + "lol" + ] + }, + { + "owner": "andi2garcia", + "repo": "OnLoL-Riot-API", + "language": "PHP", + "links": [], + "metadata": {} + }, + { + "owner": "carlos170586", + "repo": "php-lol-api", + "language": "PHP", + "links": [], + "metadata": {} + }, + { + "owner": "kevinohashi", + "repo": "php-riot-api", + "language": "PHP", + "links": [], + "metadata": {}, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "fcarrascosa", + "repo": "phpRiotApi", + "language": "PHP", + "links": [ + { + "name": "The project on gitHub", + "url": "https://github.com/fcarrascosa/phpRiotApi" + }, + { + "name": "The project on packagist", + "url": "https://packagist.org/packages/fcarrascosa/riot-api-client" + } + ], + "metadata": { + "v3-supported": true, + "author": { + "name": "Fernando Carrascosa", + "website": "https://fcarrascosa.es", + "location": "Madrid/Spain", + "twitter": "jarredethe", + "gitHub": "fcarrascosa" + }, + "version": "1.0.0" + } + }, + { + "owner": "dolejska-daniel", + "repo": "riot-api", + "description": "Riot League of Legends & DataDragon API wrappers for PHP7", + "language": "PHP", + "links": [ + { + "name": "GitHub Wiki", + "url": "https://github.com/dolejska-daniel/riot-api/wiki" + }, + { + "name": "Packagist", + "url": "https://packagist.org/packages/dolejska-daniel/riot-api" + } + ], + "metadata": { + "version": "v3.1.0" + }, + "tags": [ + "v3", + "v4", + "rate-limiting", + "cli" + ] + }, + { + "owner": "simivar", + "repo": "riot-php", + "description": "PSR-17, PSR-18 and Dependency-Injection based PHP wrapper around Riot API", + "language": "PHP", + "links": [ + { + "name": "Packagist", + "url": "https://packagist.org/packages/simivar/riot-php" + }, + { + "name": "Github", + "url": "https://github.com/simivar/riot-php" + } + ], + "metadata": { + "v3": true, + "v4": true + }, + "tags": [ + "v3", + "v4", + "psr-17", + "psr-18" + ] + }, + { + "owner": "opgginc", + "repo": "php-riotapi-request", + "description": "RiotQuest, PHP RiotAPI client library that focused on multi request from OP.GG", + "language": "PHP", + "metadata": { + "version": "0.5" + }, + "tags": [ + "v3" + ] + }, + { + "owner": "kdefives", + "repo": "oauth2-riot", + "description": "Riot (RSO) OAuth 2.0 support for the PHP League's OAuth 2.0 Client", + "language": "PHP", + "links": [ + { + "name": "Packagist", + "url": "https://packagist.org/packages/kdefives/oauth2-riot" + }, + { + "name": "Github", + "url": "https://github.com/kdefives/oauth2-riot" + } + ], + "metadata": { + "version": "1.0.0" + }, + "tags": [ + "v4", + "rso", + "oauth 2.0" + ] + }, + { + "owner": "meraki-analytics", + "repo": "cassiopeia", + "description": "Cassiopeia takes care of all the details for you so you can focus on building your application", + "language": "Python", + "links": [ + { + "name": "PyPi", + "url": "https://pypi.org/project/cassiopeia/" + }, + { + "name": "Documentation", + "url": "http://cassiopeia.readthedocs.org/en/latest/" + } + ], + "metadata": {}, + "tags": [ + "v4", + "rate-limiting", + "caching" + ] + }, + { + "owner": "sousa-andre", + "repo": "lcu-driver", + "description": "Python3 helper for the League of Legends LCU API.", + "language": "Python", + "links": [ + { + "name": "PyPI", + "url": "https://pypi.org/project/lcu-driver/" + }, + { + "name": "github", + "url": "https://github.com/sousa-andre/lcu-driver" + }, + { + "name": "Documentation", + "url": "https://lcu-driver.readthedocs.io/en/latest/" + } + ], + "metadata": {}, + "tags": [ + "lcu", + "lol", + "asyncio" + ] + }, + { + "owner": "Kruptein", + "repo": "lolapi", + "language": "Python", + "links": [], + "metadata": {} + }, + { + "owner": "p-ob", + "repo": "lolPy", + "language": "Python", + "links": [], + "metadata": {} + }, + { + "owner": "iann838", + "repo": "pulsefire", + "description": "A modern and flexible Riot Games Python SDK.", + "language": "Python", + "links": [ + { + "name": "Documentation", + "url": "https://pulsefire.iann838.com/" + }, + { + "name": "GitHub", + "url": "https://github.com/iann838/pulsefire" + }, + { + "name": "PyPi", + "url": "https://pypi.org/project/pulsefire/" + } + ], + "metadata": {}, + "tags": [ + "v4", + "rate-limiting", + "asyncio", + "lol", + "lor", + "tft", + "val" + ] + }, + { + "owner": "iann838", + "repo": "Pyot", + "description": "AsyncIO-based high-level Python framework for the Riot Games API. (Deprecated, please use pulsefire instead)", + "language": "Python", + "links": [ + { + "name": "Documentation", + "url": "https://pyot.iann838.com/" + }, + { + "name": "PyPi", + "url": "https://pypi.org/project/pyot/" + } + ], + "metadata": {}, + "tags": [ + "v4", + "rate-limiting", + "asyncio", + "lol", + "lor", + "tft", + "val" + ] + }, + { + "owner": "HeshamAmer", + "repo": "Riot-API-datasource", + "language": "Python", + "links": [], + "metadata": {} + }, + { + "owner": "pseudonym117", + "repo": "Riot-Watcher", + "description": "A thin Riot API wrapper focused on simplicity and ease of use", + "language": "Python", + "links": [ + { + "name": "Documentation", + "url": "http://riot-watcher.readthedocs.io/en/latest/" + }, + { + "name": "PyPi", + "url": "https://pypi.python.org/pypi/riotwatcher" + } + ], + "metadata": {}, + "tags": [ + "v4", + "rate-limiting", + "tft", + "lor", + "val" + ] + }, + { + "owner": "jellies", + "repo": "willump", + "description": "Python3 helper for the League of Legends LCU API.", + "language": "Python", + "links": [ + { + "name": "PyPi", + "url": "https://pypi.org/project/willump/" + }, + { + "name": "github", + "url": "https://github.com/elliejs/Willump" + }, + { + "name": "Tutorial", + "url": "https://github.com/elliejs/Willump/tree/main/tutorial" + } + ], + "metadata": {}, + "tags": [ + "lcu", + "lol", + "asyncio", + "wss", + "https", + "cors", + "live-events" + ] + }, + { + "owner": "shishi", + "repo": "riot_games_api", + "language": "Ruby", + "links": [], + "metadata": {} + }, + { + "owner": "francois-blanchard", + "repo": "riot_lol_api", + "language": "Ruby", + "links": [], + "metadata": {} + }, + { + "owner": "mikamai", + "repo": "ruby-lol", + "language": "Ruby", + "links": [], + "metadata": {} + }, + { + "owner": "josephyi", + "repo": "taric", + "language": "Ruby", + "links": [], + "metadata": {} + }, + { + "owner": "AlsoSylv", + "repo": "Irelia", + "language": "Rust", + "description": "LoL LCU Wrapper for Rust, built on top of hyper!", + "links": [ + { + "name": "docs.rs", + "url": "https://docs.rs/crate/irelia/latest" + }, + { + "name": "crates.io", + "url": "https://crates.io/crates/irelia" + }, + { + "name": "github", + "url": "https://github.com/AlsoSylv/Irelia/tree/master" + } + ], + "metadata": { + "async": true + }, + "tags": [ + "lcu", + "rest", + "websocket", + "ws", + "ingame", + "tokio", + "hyper" + ] + }, + { + "owner": "MingweiSamuel", + "repo": "Riven", + "language": "Rust", + "description": "Tried and tested Riot API design, in Rust", + "links": [ + { + "name": "Docs.rs", + "url": "https://docs.rs/riven/" + }, + { + "name": "Crates.io", + "url": "https://crates.io/crates/riven" + } + ], + "metadata": { + "v3": true, + "v4": true, + "async": true, + "rate-limiter": { + "header-driven": true, + "method-rate-limiting": true, + "per-region": true, + "per-endpoint": true + }, + "tft": true + }, + "tags": [ + "v3", + "v4", + "rate-limiting", + "lol", + "tft", + "lor", + "val" + ] + }, + { + "owner": "Leastrio", + "repo": "Shaco", + "language": "Rust", + "description": "League of Legends LCU wrapper for rust", + "links": [ + { + "name": "docs.rs", + "url": "https://docs.rs/shaco/latest/shaco/" + }, + { + "name": "crates.io", + "url": "https://crates.io/crates/shaco" + } + ], + "metadata": { + "async": true + }, + "tags": [ + "lcu", + "rest", + "websocket", + "ws", + "ingame", + "tokio" + ] + }, + { + "owner": "LionelBergen", + "repo": "ZedScript", + "language": "Rust", + "description": "Easy to use, simple, basic, tested Riot API wrapper written in Rust", + "links": [ + { + "name": "Crates.io", + "url": "https://crates.io/crates/zed_script" + } + ], + "metadata": { + "v4": true, + "async": true, + "tft": true + }, + "tags": [ + "v4", + "tft" + ] + }, + { + "owner": "WxWatch", + "repo": "DragonService", + "language": "Swift", + "links": [], + "metadata": {}, + "tags": [ + "v4" + ] + }, + { + "owner": "Kelmatou", + "repo": "LeagueAPI", + "description": "Framework providing all League of Legends data, with cache, rate-limit handling with auto retry system. Compatible with Carthage and Cocoapod.", + "language": "Swift", + "links": [ + { + "name": "Github", + "url": "https://github.com/Kelmatou/LeagueAPI" + }, + { + "name": "Documentation", + "url": "https://github.com/Kelmatou/LeagueAPI/wiki" + } + ], + "metadata": {}, + "tags": [ + "v4", + "rate-limiting" + ] + }, + { + "owner": "bcho04", + "repo": "galeforce", + "language": "TypeScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/galeforce" + }, + { + "name": "github", + "url": "https://github.com/bcho04/galeforce" + }, + { + "name": "docs", + "url": "https://bcho04.github.io/galeforce/" + } + ], + "metadata": { + "version": "0.5.0" + }, + "tags": [ + "v4", + "v5", + "rate-limiting", + "lol", + "lor", + "val", + "tft", + "cache" + ] + }, + { + "owner": "dysolix", + "repo": "hasagi-core", + "description": "LCU library with auto-generated types for request parameters and responses", + "language": "TypeScript", + "links": [ + { + "name": "GitHub", + "url": "https://github.com/dysolix/hasagi-core" + }, + { + "name": "Docs", + "url": "https://github.com/dysolix/hasagi-core/wiki" + }, + { + "name": "Extended Version GitHub", + "url": "https://github.com/dysolix/hasagi-extended" + } + ], + "metadata": {}, + "tags": [ + "lcu" + ] + }, + { + "owner": "cuppachino", + "repo": "hexgate", + "description": "LCU API wrapper for League of Legends", + "language": "TypeScript", + "links": [ + { + "name": "GitHub", + "url": "https://github.com/cuppachino/hexgate" + }, + { + "name": "NPM", + "url": "https://www.npmjs.com/package/hexgate" + } + ], + "metadata": { + "version": "0.7.7" + }, + "tags": [ + "lol", + "lcu", + "websocket", + "auth", + "authentication", + "ts" + ] + }, + { + "owner": "SKarajic", + "repo": "JarvanScript", + "description": "A TypeScript Riot API wrapper", + "language": "TypeScript", + "links": [ + { + "name": "GitHub", + "url": "https://github.com/SKarajic/JarvanScript" + }, + { + "name": "NPM", + "url": "https://www.npmjs.com/package/jarvanscript" + } + ], + "metadata": { + "version": "0.4.2-alpha" + }, + "tags": [ + "v3", + "rate-limiting" + ] + }, + { + "owner": "protectator", + "repo": "league-typedef", + "description": "Typescript type definitions for the League of Legends API.", + "language": "TypeScript", + "links": [ + { + "name": "GitHub", + "url": "https://github.com/Protectator/league-typedef" + }, + { + "name": "npm", + "url": "https://www.npmjs.com/package/league-typedef" + } + ], + "metadata": {} + }, + { + "owner": "danielsogl", + "repo": "lol-api-wrapper", + "description": "A ready to run LoL API wrapper powered by Express.js and TypeScript", + "language": "TypeScript", + "links": [ + { + "name": "GitHub", + "url": "https://github.com/danielsogl/lol-api-wrapper" + } + ], + "tags": [ + "v3", + "caching", + "ratelimit", + "heroku", + "express", + "typescript", + "redis" + ] + }, + { + "owner": "TheDrone7", + "repo": "shieldbow", + "description": "A super easy-to-use RIOT API Wrapper with full type support.", + "language": "TypeScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/shieldbow" + }, + { + "name": "Documentation", + "url": "https://thedrone7.github.io/shieldbow/" + } + ], + "metadata": { + "version": "1.2.0" + }, + "tags": [ + "v4", + "v5", + "rate-limiting", + "caching", + "lol", + "ts" + ] + }, + { + "owner": "Sansossio", + "repo": "twisted", + "language": "TypeScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/twisted" + }, + { + "name": "github", + "url": "https://github.com/Sansossio/twisted" + }, + { + "name": "examples", + "url": "https://github.com/Sansossio/twisted/tree/master/example" + } + ], + "metadata": { + "version": "2.0.0" + }, + "tags": [ + "rate-limiting", + "v4", + "lol", + "tft", + "caching" + ] + }, + { + "owner": "NitashEU", + "repo": "zed.gg", + "language": "TypeScript", + "links": [ + { + "name": "npm", + "url": "https://www.npmjs.com/package/zed.gg" + } + ], + "metadata": { + "v3": "true", + "rate-limiter": { + "per-region": true, + "burst": false, + "spread": true, + "header-validation": true + } + } + } +] \ No newline at end of file diff --git a/Needlework.Net/Converters/EnumerableBoolConverter.cs b/Needlework.Net/Converters/EnumerableToVisibility.cs similarity index 90% rename from Needlework.Net/Converters/EnumerableBoolConverter.cs rename to Needlework.Net/Converters/EnumerableToVisibility.cs index b1231a9..56c27a7 100644 --- a/Needlework.Net/Converters/EnumerableBoolConverter.cs +++ b/Needlework.Net/Converters/EnumerableToVisibility.cs @@ -6,7 +6,7 @@ using System.Linq; namespace Needlework.Net.Converters { - public class EnumerableBoolConverter : IValueConverter + public class EnumerableToVisibility : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { diff --git a/Needlework.Net/Converters/NullBoolConverter.cs b/Needlework.Net/Converters/NullableToVisibility.cs similarity index 89% rename from Needlework.Net/Converters/NullBoolConverter.cs rename to Needlework.Net/Converters/NullableToVisibility.cs index d105d03..715afd7 100644 --- a/Needlework.Net/Converters/NullBoolConverter.cs +++ b/Needlework.Net/Converters/NullableToVisibility.cs @@ -4,7 +4,7 @@ using System.Globalization; namespace Needlework.Net.Converters { - public class NullBoolConverter : IValueConverter + public class NullableToVisibility : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { diff --git a/Needlework.Net/DataSource.cs b/Needlework.Net/DataSource.cs deleted file mode 100644 index b9eba09..0000000 --- a/Needlework.Net/DataSource.cs +++ /dev/null @@ -1,60 +0,0 @@ -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 _logger; - private readonly HttpClient _httpClient; - private Document? _lcuSchemaDocument; - private Document? _lolClientDocument; - private readonly TaskCompletionSource _taskCompletionSource = new(); - - - public DataSource(HttpClient httpClient, ILogger logger) - { - _httpClient = httpClient; - _logger = logger; - } - - public async Task GetLcuSchemaDocumentAsync() - { - await _taskCompletionSource.Task; - return _lcuSchemaDocument ?? throw new InvalidOperationException(); - } - - public async Task 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); - } - } - } -} diff --git a/Needlework.Net/Extensions/EnableLoggerExtensions.cs b/Needlework.Net/Extensions/EnableLoggerExtensions.cs new file mode 100644 index 0000000..e7ee181 --- /dev/null +++ b/Needlework.Net/Extensions/EnableLoggerExtensions.cs @@ -0,0 +1,16 @@ +using Serilog; + +namespace Needlework.Net.Extensions +{ + public static class EnableLoggerExtensions + { + private static readonly ILogger _logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.File(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}", path: "Logs/debug-.log", rollingInterval: RollingInterval.Day, shared: true) + .CreateLogger(); + + public static ILogger Log(this IEnableLogger? context) => _logger.ForContext(context?.GetType() ?? typeof(Program)); + } + + public interface IEnableLogger; +} diff --git a/Needlework.Net/Extensions/ServiceCollectionExtensions.cs b/Needlework.Net/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index a2f50af..0000000 --- a/Needlework.Net/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; - -namespace Needlework.Net.Extensions -{ - public static class ServiceCollectionExtensions - { - public static IServiceCollection AddSingletonsFromAssemblies(this ServiceCollection services) - { - var types = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(s => s.GetTypes()) - .Where(p => !p.IsAbstract && typeof(T).IsAssignableFrom(p)); - - foreach (var type in types) services.AddSingleton(typeof(T), type); - - return services; - } - } -} \ No newline at end of file diff --git a/Needlework.Net/Logger.cs b/Needlework.Net/Logger.cs deleted file mode 100644 index 07ab398..0000000 --- a/Needlework.Net/Logger.cs +++ /dev/null @@ -1,27 +0,0 @@ -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()); - } - } -} diff --git a/Needlework.Net/Messages/InfoBarUpdateMessage.cs b/Needlework.Net/Messages/InfoBarUpdateMessage.cs deleted file mode 100644 index ed2d354..0000000 --- a/Needlework.Net/Messages/InfoBarUpdateMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -using CommunityToolkit.Mvvm.Messaging.Messages; -using Needlework.Net.ViewModels.MainWindow; - -namespace Needlework.Net.Messages -{ - public class InfoBarUpdateMessage(InfoBarViewModel vm) : ValueChangedMessage(vm) - { - } -} diff --git a/Needlework.Net/Models/GithubRelease.cs b/Needlework.Net/Models/GithubRelease.cs index 55f58d3..6f8c93d 100644 --- a/Needlework.Net/Models/GithubRelease.cs +++ b/Needlework.Net/Models/GithubRelease.cs @@ -7,6 +7,6 @@ namespace Needlework.Net.Models [JsonPropertyName("tag_name")] public string TagName { get; set; } = string.Empty; - public bool IsLatest(int version) => int.Parse(TagName.Replace(".", "")) > version; + public bool IsLatest(string assemblyVersion) => int.Parse(TagName.Replace(".", "")) > int.Parse(assemblyVersion.ToString().Replace(".", "")); } } diff --git a/Needlework.Net/Models/Library.cs b/Needlework.Net/Models/Library.cs index 6242124..2d5e79a 100644 --- a/Needlework.Net/Models/Library.cs +++ b/Needlework.Net/Models/Library.cs @@ -1,9 +1,31 @@ -namespace Needlework.Net.Models; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Needlework.Net.Models; public class Library { + [JsonPropertyName("repo")] public required string Repo { get; init; } + + [JsonPropertyName("description")] public string? Description { get; init; } + + [JsonPropertyName("language")] public required string Language { get; init; } - public required string Link { get; init; } + + [JsonPropertyName("owner")] + public required string Owner { get; init; } + + [JsonPropertyName("tags")] + public List Tags { get; init; } = []; + + public string Link + { + get + { + if (Owner.Equals("jellies")) return $"https://github.com/elliejs/{Repo}"; + return $"https://github.com/{Owner}/{Repo}"; + } + } } diff --git a/Needlework.Net/Models/Notification.cs b/Needlework.Net/Models/Notification.cs new file mode 100644 index 0000000..6423c30 --- /dev/null +++ b/Needlework.Net/Models/Notification.cs @@ -0,0 +1,9 @@ +using FluentAvalonia.UI.Controls; +using System; + +namespace Needlework.Net.Models +{ + public record Notification(string Title, string Message, InfoBarSeverity InfoBarSeverity, TimeSpan? Duration = null, string? Url = null) + { + } +} diff --git a/Needlework.Net/Needlework.Net.csproj b/Needlework.Net/Needlework.Net.csproj index dc7e0fd..4111108 100644 --- a/Needlework.Net/Needlework.Net.csproj +++ b/Needlework.Net/Needlework.Net.csproj @@ -31,6 +31,8 @@ + + @@ -67,11 +69,17 @@ MainWindowView.axaml - - EndpointsNavigationView.axaml + + EndpointListView.axaml - - EndpointView.axaml + + EndpointsView.axaml + + + PluginView.axaml + + + WebSocketView.axaml diff --git a/Needlework.Net/Program.cs b/Needlework.Net/Program.cs index 470d40c..cb19687 100644 --- a/Needlework.Net/Program.cs +++ b/Needlework.Net/Program.cs @@ -1,13 +1,21 @@ using Avalonia; +using Avalonia.Controls.Templates; +using Flurl.Http.Configuration; using Microsoft.Extensions.DependencyInjection; using Needlework.Net.Extensions; using Needlework.Net.Services; using Needlework.Net.ViewModels.MainWindow; using Needlework.Net.ViewModels.Pages; +using Needlework.Net.ViewModels.Pages.About; +using Needlework.Net.ViewModels.Pages.Console; +using Needlework.Net.ViewModels.Pages.Endpoints; +using Needlework.Net.ViewModels.Pages.Home; +using Needlework.Net.ViewModels.Pages.WebSocket; using Projektanker.Icons.Avalonia; using Projektanker.Icons.Avalonia.FontAwesome; +using Serilog; using System; -using System.Threading.Tasks; +using System.IO; namespace Needlework.Net; @@ -22,54 +30,58 @@ class Program AppDomain.CurrentDomain.UnhandledException += Program_UnhandledException; BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + .StartWithClassicDesktopLifetime(args); } // Avalonia configuration, don't remove; also used by visual designer. public static AppBuilder BuildAvaloniaApp() { - IconProvider.Current - .Register(); - var services = BuildServices(); - Task.Run(async () => await InitializeDataSourceAsync(services)); + IconProvider.Current.Register(); - return AppBuilder.Configure(() => new App(services)) + return AppBuilder.Configure(() => new App(BuildServices())) .UsePlatformDetect() .WithInterFont() - .With(new Win32PlatformOptions - { - CompositionMode = [Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition] - }) - .With(new MacOSPlatformOptions - { - ShowInDock = true, - }) + .With(new Win32PlatformOptions { CompositionMode = [Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition] }) + .With(new MacOSPlatformOptions { ShowInDock = true, }) .LogToTrace(); } - private static async Task InitializeDataSourceAsync(IServiceProvider services) - { - var dataSource = services.GetRequiredService(); - await dataSource.InitializeAsync(); - } - private static IServiceProvider BuildServices() { var builder = new ServiceCollection(); + AddViewModels(builder); + AddServices(builder); - builder.AddSingleton(); + return builder.BuildServiceProvider(); + } + + private static void AddServices(ServiceCollection builder) + { builder.AddSingleton(); - builder.AddSingleton(); - builder.AddSingletonsFromAssemblies(); - builder.AddHttpClient(); - builder.AddLogging(Logger.Setup); + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(new FlurlClientCache() + .Add("GithubClient", "https://api.github.com") + .Add("GithubUserContentClient", "https://raw.githubusercontent.com") + .Add("Client")); - var services = builder.BuildServiceProvider(); - return services; + builder.AddLogging((builder) => builder.AddSerilog(EnableLoggerExtensions.Log(null))); + builder.AddSingleton(new ViewLocator()); + } + + private static void AddViewModels(ServiceCollection builder) + { + builder.AddSingleton(); + + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); } private static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e) { - Logger.LogFatal(e); + File.WriteAllText($"Logs/fatal-{DateTime.Now:yyyyMMdd}.log", e.ExceptionObject.ToString()); } } diff --git a/Needlework.Net/Services/DocumentService.cs b/Needlework.Net/Services/DocumentService.cs new file mode 100644 index 0000000..291e7c8 --- /dev/null +++ b/Needlework.Net/Services/DocumentService.cs @@ -0,0 +1,53 @@ +using FastCache; +using Flurl.Http; +using Flurl.Http.Configuration; +using Microsoft.OpenApi.Readers; +using Needlework.Net.Extensions; +using Needlework.Net.Models; +using System; +using System.Threading.Tasks; + +namespace Needlework.Net +{ + public class DocumentService : IEnableLogger + { + private readonly OpenApiStreamReader _reader = new(); + + private readonly IFlurlClient _githubUserContentClient; + + public DocumentService(IFlurlClientCache clients) + { + _githubUserContentClient = clients.Get("GithubUserContentClient"); + } + + public async Task GetLcuSchemaDocumentAsync() + { + if (Cached.TryGet(nameof(GetLcuSchemaDocumentAsync), out var cached)) + { + return cached; + } + + var lcuSchemaStream = await _githubUserContentClient.Request("/dysolix/hasagi-types/main/swagger.json") + .GetStreamAsync(); + var lcuSchemaRaw = _reader.Read(lcuSchemaStream, out var _); + var document = new Document(lcuSchemaRaw); + + return cached.Save(document, TimeSpan.FromMinutes(60)); + } + + public async Task GetLolClientDocumentAsync() + { + if (Cached.TryGet(nameof(GetLolClientDocumentAsync), out var cached)) + { + return cached; + } + + var lolClientStream = await _githubUserContentClient.Request("/AlsoSylv/Irelia/refs/heads/master/schemas/game_schema.json") + .GetStreamAsync(); + var lolClientRaw = _reader.Read(lolClientStream, out var _); + var document = new Document(lolClientRaw); + + return cached.Save(document, TimeSpan.FromMinutes(60)); + } + } +} diff --git a/Needlework.Net/Services/NotificationService.cs b/Needlework.Net/Services/NotificationService.cs new file mode 100644 index 0000000..74993e0 --- /dev/null +++ b/Needlework.Net/Services/NotificationService.cs @@ -0,0 +1,20 @@ +using FluentAvalonia.UI.Controls; +using Needlework.Net.Models; +using System; +using System.Reactive.Subjects; + +namespace Needlework.Net.Services +{ + public class NotificationService + { + private readonly Subject _notificationSubject = new(); + + public IObservable Notifications { get { return _notificationSubject; } } + + public void Notify(string title, string message, InfoBarSeverity severity, TimeSpan? duration = null, string? url = null) + { + var notification = new Notification(title, message, severity, duration, url); + _notificationSubject.OnNext(notification); + } + } +} diff --git a/Needlework.Net/ViewLocator.cs b/Needlework.Net/ViewLocator.cs index 14f5a17..c1c6949 100644 --- a/Needlework.Net/ViewLocator.cs +++ b/Needlework.Net/ViewLocator.cs @@ -17,11 +17,11 @@ namespace Needlework.Net var name = data?.GetType().Name; if (name is null) { - return new TextBlock { Text = "Data is null or has no name." }; + throw new Exception("Data type name is null."); } if (!name.Contains("ViewModel")) { - return new TextBlock { Text = "Data name must end with ViewModel." }; + throw new Exception("Data type name must end with 'ViewModel'."); } name = name.Replace("ViewModel", "View"); @@ -32,9 +32,8 @@ namespace Needlework.Net if (type is null) { - return new TextBlock { Text = $"No view for {name}." }; + throw new Exception("Data type has no view."); } - if (!_controlCache.TryGetValue(data!, out var res)) { res ??= (Control)Activator.CreateInstance(type)!; diff --git a/Needlework.Net/ViewModels/MainWindow/InfoBarViewModel.cs b/Needlework.Net/ViewModels/MainWindow/InfoBarViewModel.cs deleted file mode 100644 index 9ccf17d..0000000 --- a/Needlework.Net/ViewModels/MainWindow/InfoBarViewModel.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Avalonia.Controls; -using CommunityToolkit.Mvvm.ComponentModel; -using FluentAvalonia.UI.Controls; -using System; - -namespace Needlework.Net.ViewModels.MainWindow; - -public partial class InfoBarViewModel : ObservableObject -{ - [ObservableProperty] private string _title; - [ObservableProperty] private bool _isOpen; - [ObservableProperty] private string _message; - [ObservableProperty] private InfoBarSeverity _severity; - [ObservableProperty] private TimeSpan _duration; - [ObservableProperty] private Control? _actionButton; - - public InfoBarViewModel(string title, bool isOpen, string message, InfoBarSeverity severity, TimeSpan duration, Control? actionButton = null) - { - _title = title; - _isOpen = isOpen; - _message = message; - _severity = severity; - _duration = duration; - _actionButton = actionButton; - } -} diff --git a/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs b/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs index e1e84bd..cde5527 100644 --- a/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs +++ b/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs @@ -1,11 +1,12 @@ -using Avalonia.Collections; -using BlossomiShymae.Briar; +using BlossomiShymae.Briar; using BlossomiShymae.Briar.Utils; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using FluentAvalonia.UI.Controls; -using Microsoft.Extensions.Logging; +using Flurl.Http; +using Flurl.Http.Configuration; +using Needlework.Net.Extensions; using Needlework.Net.Messages; using Needlework.Net.Models; using Needlework.Net.Services; @@ -16,203 +17,178 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; -using System.Net.Http; using System.Net.Http.Json; +using System.Reactive; +using System.Reactive.Linq; using System.Reflection; using System.Threading.Tasks; -using System.Timers; namespace Needlework.Net.ViewModels.MainWindow; public partial class MainWindowViewModel - : ObservableObject, IRecipient, IRecipient + : ObservableObject, IRecipient, IEnableLogger { - public IAvaloniaReadOnlyList MenuItems { get; } - [ObservableProperty] private NavigationViewItem _selectedMenuItem; - [ObservableProperty] private PageBase _currentPage; + private readonly DocumentService _documentService; - public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0"; - [ObservableProperty] private bool _isUpdateShown = false; + private readonly IFlurlClient _githubClient; - [ObservableProperty] private string _schemaVersion = "N/A"; - [ObservableProperty] private string _schemaVersionLatest = "N/A"; + private readonly NotificationService _notificationService; - public HttpClient HttpClient { get; } - public DialogService DialogService { get; } + private readonly DialogService _dialogService; - private readonly DataSource _dataSource; + private readonly IDisposable _checkForUpdatesDisposable; - [ObservableProperty] private bool _isBusy = true; + private readonly IDisposable _checkForSchemaVersionDisposable; - [ObservableProperty] private ObservableCollection _infoBarItems = []; - - private readonly ILogger _logger; - - private readonly System.Timers.Timer _latestUpdateTimer = new() + public MainWindowViewModel(IEnumerable pages, DialogService dialogService, DocumentService documentService, NotificationService notificationService, IFlurlClientCache clients) { - Interval = TimeSpan.FromMinutes(10).TotalMilliseconds, - Enabled = true - }; + _dialogService = dialogService; + _documentService = documentService; + _notificationService = notificationService; + _githubClient = clients.Get("GithubClient"); - private readonly System.Timers.Timer _schemaVersionTimer = new() - { - Interval = TimeSpan.FromSeconds(5).TotalMilliseconds, - Enabled = true - }; - private bool _isSchemaVersionChecked = false; - - public MainWindowViewModel(IEnumerable pages, HttpClient httpClient, DialogService dialogService, ILogger logger, DataSource dataSource) - { - _logger = logger; - _dataSource = dataSource; - - MenuItems = new AvaloniaList(pages + NavigationViewItems = pages .OrderBy(p => p.Index) .ThenBy(p => p.DisplayName) - .Select(p => new NavigationViewItem() - { - Content = p.DisplayName, - Tag = p, - IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") } - })); - SelectedMenuItem = MenuItems[0]; - CurrentPage = (PageBase)MenuItems[0].Tag!; + .Select(ToNavigationViewItem) + .ToList(); + SelectedNavigationViewItem = NavigationViewItems.First(); + CurrentPage = (PageBase)SelectedNavigationViewItem.Tag!; - HttpClient = httpClient; - DialogService = dialogService; + _notificationService.Notifications.Subscribe(async notification => + { + var vm = new NotificationViewModel(notification); + Notifications.Add(vm); + await Task.Delay(notification.Duration ?? TimeSpan.FromSeconds(10)); + Notifications.Remove(vm); + }); + + _checkForUpdatesDisposable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(10)) + .Select(time => Unit.Default) + .Subscribe(async _ => + { + try + { + await CheckForUpdatesAsync(); + } + catch (Exception ex) + { + var message = "Failed to check for updates. Please check your internet connection or try again later."; + this.Log() + .Error(ex, message); + _notificationService.Notify("Needlework.Net", message, InfoBarSeverity.Error); + _checkForUpdatesDisposable?.Dispose(); + } + }); + + _checkForSchemaVersionDisposable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(10)) + .Select(time => Unit.Default) + .Subscribe(async _ => + { + try + { + await CheckForSchemaVersionAsync(); + } + catch (Exception ex) + { + var message = "Failed to check for schema version. Please check your internet connection or try again later."; + this.Log() + .Error(ex, message); + _notificationService.Notify("Needlework.Net", message, InfoBarSeverity.Error); + _checkForSchemaVersionDisposable?.Dispose(); + } + }); WeakReferenceMessenger.Default.RegisterAll(this); - - _latestUpdateTimer.Elapsed += OnLatestUpdateTimerElapsed; - _schemaVersionTimer.Elapsed += OnSchemaVersionTimerElapsed; - _latestUpdateTimer.Start(); - _schemaVersionTimer.Start(); - OnLatestUpdateTimerElapsed(null, null); - OnSchemaVersionTimerElapsed(null, null); - } - partial void OnSelectedMenuItemChanged(NavigationViewItem value) + [ObservableProperty] + private ObservableCollection _notifications = []; + + [ObservableProperty] + private NavigationViewItem _selectedNavigationViewItem; + + [ObservableProperty] + private PageBase _currentPage; + + public List NavigationViewItems { get; private set; } = []; + + public bool IsSchemaVersionChecked { get; private set; } + + public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0"; + + public string Title => $"Needlework.Net {Version}"; + + private NavigationViewItem ToNavigationViewItem(PageBase page) => new() + { + Content = page.DisplayName, + Tag = page, + IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{page.Icon}.png") } + }; + + partial void OnSelectedNavigationViewItemChanged(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 Task CheckForUpdatesAsync() + { + var release = await _githubClient + .Request("/repos/BlossomiShymae/Needlework.Net/releases/latest") + .WithHeader("User-Agent", $"Needlework.Net/{Version}") + .GetJsonAsync(); + + if (release.IsLatest(Version)) + { + this.Log() + .Information("New version available: {TagName}", release.TagName); + _notificationService.Notify("Needlework.Net", $"New version available: {release.TagName}", InfoBarSeverity.Informational, null, "https://github.com/BlossomiShymae/Needlework.Net/releases/latest"); + _checkForUpdatesDisposable?.Dispose(); + } + } + + + private async Task CheckForSchemaVersionAsync() { if (!ProcessFinder.IsPortOpen()) return; - var lcuSchemaDocument = await _dataSource.GetLcuSchemaDocumentAsync(); - try + var lcuSchemaDocument = await _documentService.GetLcuSchemaDocumentAsync(); + var client = Connector.GetLcuHttpClientInstance(); + var currentSemVer = lcuSchemaDocument.Info.Version.Split('.'); + var systemBuild = await client.GetFromJsonAsync("/system/v1/builds") ?? throw new NullReferenceException(); + var latestSemVer = systemBuild.Version.Split('.'); + + if (!IsSchemaVersionChecked) { - var client = Connector.GetLcuHttpClientInstance(); - - var currentSemVer = lcuSchemaDocument.Info.Version.Split('.'); - var systemBuild = await client.GetFromJsonAsync("/system/v1/builds") ?? throw new NullReferenceException(); - var latestSemVer = systemBuild.Version.Split('.'); - - if (!_isSchemaVersionChecked) - { - _logger.LogInformation("LCU Schema (current): {Version}", lcuSchemaDocument.Info.Version); - _logger.LogInformation("LCU Schema (latest): {Version}", systemBuild.Version); - _isSchemaVersionChecked = true; - } - - bool isVersionMatching = currentSemVer[0] == latestSemVer[0] && currentSemVer[1] == latestSemVer[1]; // Compare major and minor versions - if (!isVersionMatching) - { - Avalonia.Threading.Dispatcher.UIThread.Post(async () => - { - await ShowInfoBarAsync(new("Newer System Build", true, $"LCU Schema is possibly outdated compared to latest system build. Consider submitting a pull request on dysolix/hasagi-types.\nCurrent: {string.Join(".", currentSemVer)}\nLatest: {string.Join(".", latestSemVer)}", InfoBarSeverity.Warning, TimeSpan.FromSeconds(60), new Avalonia.Controls.Button() - { - Command = OpenUrlCommand, - CommandParameter = "https://github.com/dysolix/hasagi-types#updating-the-types", - Content = "Submit PR" - })); - }); - - _schemaVersionTimer.Elapsed -= OnSchemaVersionTimerElapsed; - _schemaVersionTimer.Stop(); - } + this.Log() + .Information("LCU Schema (current): {Version}", lcuSchemaDocument.Info.Version); + this.Log() + .Information("LCU Schema (latest): {Version}", systemBuild.Version); + IsSchemaVersionChecked = true; } - catch (Exception ex) + + bool isVersionMatching = currentSemVer[0] == latestSemVer[0] && currentSemVer[1] == latestSemVer[1]; // Compare major and minor versions + if (!isVersionMatching) { - _logger.LogError(ex, "Schema version check failed"); - } - } - - private async void OnLatestUpdateTimerElapsed(object? sender, ElapsedEventArgs? e) - { - try - { - var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/BlossomiShymae/Needlework.Net/releases/latest"); - request.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("Needlework.Net", Version)); - - var response = await HttpClient.SendAsync(request); - var release = await response.Content.ReadFromJsonAsync(); - if (release == null) - { - _logger.LogWarning("Release response is null"); - return; - } - - var currentVersion = int.Parse(Version.Replace(".", "")); - - if (release.IsLatest(currentVersion)) - { - Avalonia.Threading.Dispatcher.UIThread.Post(async () => - { - await ShowInfoBarAsync(new("Needlework.Net Update", true, $"There is a new version available: {release.TagName}.", InfoBarSeverity.Informational, TimeSpan.FromSeconds(30), new Avalonia.Controls.Button() - { - Command = OpenUrlCommand, - CommandParameter = "https://github.com/BlossomiShymae/Needlework.Net/releases", - Content = "Download" - })); - }); - - _latestUpdateTimer.Elapsed -= OnLatestUpdateTimerElapsed; - _latestUpdateTimer.Stop(); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to check for latest version"); + this.Log() + .Warning("LCU Schema version mismatch: Current {CurrentVersion}, Latest {LatestVersion}", lcuSchemaDocument.Info.Version, systemBuild.Version); + _notificationService.Notify("Needlework.Net", $"LCU Schema is possibly outdated compared to latest system build. Consider submitting a pull request on dysolix/hasagi-types.\nCurrent: {string.Join(".", currentSemVer)}\nLatest: {string.Join(".", latestSemVer)}", InfoBarSeverity.Warning, null, "https://github.com/dysolix/hasagi-types#updating-the-types"); + _checkForSchemaVersionDisposable?.Dispose(); } } [RelayCommand] private void OpenUrl(string url) { - var process = new Process() - { - StartInfo = new ProcessStartInfo(url) - { - UseShellExecute = true - } - }; + var process = new Process() { StartInfo = new ProcessStartInfo(url) { UseShellExecute = true } }; process.Start(); } - public void Receive(InfoBarUpdateMessage message) - { - Avalonia.Threading.Dispatcher.UIThread.Post(async () => await ShowInfoBarAsync(message.Value)); - } - - private async Task ShowInfoBarAsync(InfoBarViewModel vm) - { - InfoBarItems.Add(vm); - await Task.Delay(vm.Duration); - InfoBarItems.Remove(vm); - } - public void Receive(OopsiesDialogRequestedMessage message) { - Avalonia.Threading.Dispatcher.UIThread.Invoke(async () => await DialogService.ShowAsync(message.Value)); + Avalonia.Threading.Dispatcher.UIThread.Invoke(async () => await _dialogService.ShowAsync(message.Value)); } } diff --git a/Needlework.Net/ViewModels/MainWindow/NotificationViewModel.cs b/Needlework.Net/ViewModels/MainWindow/NotificationViewModel.cs new file mode 100644 index 0000000..1d2833e --- /dev/null +++ b/Needlework.Net/ViewModels/MainWindow/NotificationViewModel.cs @@ -0,0 +1,27 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Needlework.Net.Models; +using System.Diagnostics; + +namespace Needlework.Net.ViewModels.MainWindow +{ + public partial class NotificationViewModel : ObservableObject + { + public NotificationViewModel(Notification notification) + { + Notification = notification; + IsButtonVisible = !string.IsNullOrEmpty(notification.Url); + } + + public bool IsButtonVisible { get; } + + public Notification Notification { get; } + + [RelayCommand] + public void OpenUrl() + { + var process = new Process() { StartInfo = new() { UseShellExecute = true } }; + process.Start(); + } + } +} diff --git a/Needlework.Net/ViewModels/Pages/About/AboutViewModel.cs b/Needlework.Net/ViewModels/Pages/About/AboutViewModel.cs new file mode 100644 index 0000000..b738f72 --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/About/AboutViewModel.cs @@ -0,0 +1,24 @@ +using CommunityToolkit.Mvvm.Input; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace Needlework.Net.ViewModels.Pages.About; + +public partial class AboutViewModel : PageBase +{ + public AboutViewModel() : base("About", "info-circle") + { + } + + public override Task InitializeAsync() + { + return Task.CompletedTask; + } + + [RelayCommand] + private void OpenUrl(string url) + { + var process = new Process() { StartInfo = new ProcessStartInfo(url) { UseShellExecute = true } }; + process.Start(); + } +} diff --git a/Needlework.Net/ViewModels/Pages/AboutViewModel.cs b/Needlework.Net/ViewModels/Pages/AboutViewModel.cs deleted file mode 100644 index 7b43894..0000000 --- a/Needlework.Net/ViewModels/Pages/AboutViewModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CommunityToolkit.Mvvm.Input; -using System.Diagnostics; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Needlework.Net.ViewModels.Pages; - -public partial class AboutViewModel : PageBase -{ - public HttpClient HttpClient { get; } - - public AboutViewModel(HttpClient httpClient) : base("About", "info-circle") - { - HttpClient = httpClient; - } - - public override Task InitializeAsync() - { - IsInitialized = true; - return Task.CompletedTask; - } - - [RelayCommand] - private void OpenUrl(string url) - { - var process = new Process() - { - StartInfo = new ProcessStartInfo(url) { UseShellExecute = true } - }; - process.Start(); - } -} diff --git a/Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs b/Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs new file mode 100644 index 0000000..12bfecd --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs @@ -0,0 +1,45 @@ +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Needlework.Net.Services; +using Needlework.Net.ViewModels.Shared; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Needlework.Net.ViewModels.Pages.Console; + +public partial class ConsoleViewModel : PageBase +{ + private readonly DocumentService _documentService; + + public ConsoleViewModel(DocumentService documentService, NotificationService notificationService) : base("Console", "terminal", -200) + { + _request = new(notificationService, Endpoints.Tab.LCU); + _documentService = documentService; + } + + public List RequestMethods { get; } = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]; + + public List RequestPaths { get; } = []; + + [ObservableProperty] private bool _isBusy = true; + + [ObservableProperty] private RequestViewModel _request; + + public override async Task InitializeAsync() + { + var document = await _documentService.GetLcuSchemaDocumentAsync(); + Dispatcher.UIThread.Invoke(() => + { + RequestPaths.Clear(); + RequestPaths.AddRange(document.Paths); + }); + IsBusy = false; + } + + [RelayCommand] + private async Task SendRequest() + { + await Request.ExecuteAsync(); + } +} diff --git a/Needlework.Net/ViewModels/Pages/ConsoleViewModel.cs b/Needlework.Net/ViewModels/Pages/ConsoleViewModel.cs deleted file mode 100644 index 18efa6b..0000000 --- a/Needlework.Net/ViewModels/Pages/ConsoleViewModel.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Avalonia.Collections; -using Avalonia.Threading; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.Logging; -using Needlework.Net.ViewModels.Shared; -using System.Threading.Tasks; - -namespace Needlework.Net.ViewModels.Pages; - -public partial class ConsoleViewModel : PageBase -{ - public IAvaloniaReadOnlyList RequestMethods { get; } = new AvaloniaList(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]); - public IAvaloniaList RequestPaths { get; } = new AvaloniaList(); - - [ObservableProperty] private bool _isBusy = true; - [ObservableProperty] private RequestViewModel _request; - - private readonly DataSource _dataSource; - - public ConsoleViewModel(ILogger requestViewModelLogger, DataSource dataSource) : base("Console", "terminal", -200) - { - _request = new(requestViewModelLogger, Endpoints.Tab.LCU); - _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] - private async Task SendRequest() - { - await Request.ExecuteAsync(); - } -} diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointListViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointListViewModel.cs new file mode 100644 index 0000000..b20d6cf --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointListViewModel.cs @@ -0,0 +1,64 @@ +using AvaloniaEdit.Utils; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Needlework.Net.Models; +using Needlework.Net.Services; +using System; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Needlework.Net.ViewModels.Pages.Endpoints; + +public partial class EndpointListViewModel : ObservableObject +{ + private readonly Document _document; + + private readonly Tab _tab; + + private readonly Action _onClicked; + + private readonly ObservableCollection _plugins; + + private readonly NotificationService _notificationService; + + public EndpointListViewModel(NotificationService notificationService, ObservableCollection plugins, Action onClicked, Models.Document document, Tab tab) + { + _plugins = new ObservableCollection(plugins); + _document = document; + _tab = tab; + _onClicked = onClicked; + _notificationService = notificationService; + + Plugins = EndpointSearchDetails = new ObservableCollection(plugins.Select(plugin => new EndpointSearchDetailsViewModel(notificationService, document, tab, onClicked, plugin))); + } + + public ObservableCollection Plugins { get; } + + [ObservableProperty] + private ObservableCollection _endpointSearchDetails = []; + + [ObservableProperty] + private string _search = string.Empty; + + partial void OnSearchChanged(string value) + { + EndpointSearchDetails.Clear(); + if (!string.IsNullOrEmpty(Search)) + { + EndpointSearchDetails.AddRange(_plugins.Where(plugin => plugin.Contains(value, StringComparison.InvariantCultureIgnoreCase)) + .Select(plugin => new EndpointSearchDetailsViewModel(_notificationService, _document, _tab, _onClicked, plugin))); + } + else + { + EndpointSearchDetails.AddRange( + _plugins.Select(plugin => new EndpointSearchDetailsViewModel(_notificationService, _document, _tab, _onClicked, plugin))); + } + } + + [RelayCommand] + private void OpenEndpoint(string? value) + { + if (string.IsNullOrEmpty(value)) return; + _onClicked.Invoke(new PluginViewModel(_notificationService, value, _document, _tab)); + } +} diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointSearchDetailsViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointSearchDetailsViewModel.cs new file mode 100644 index 0000000..af89e8b --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointSearchDetailsViewModel.cs @@ -0,0 +1,38 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Needlework.Net.Models; +using Needlework.Net.Services; +using System; + +namespace Needlework.Net.ViewModels.Pages.Endpoints +{ + public partial class EndpointSearchDetailsViewModel : ObservableObject + { + private readonly Document _document; + + private readonly Tab _tab; + + private readonly Action _onClicked; + + private readonly NotificationService _notificationService; + + public EndpointSearchDetailsViewModel(Services.NotificationService notificationService, Document document, Tab tab, Action onClicked, string? plugin) + { + _document = document; + _tab = tab; + _onClicked = onClicked; + _plugin = plugin; + _notificationService = notificationService; + } + + [ObservableProperty] + private string? _plugin; + + [RelayCommand] + private void OpenEndpoint() + { + if (string.IsNullOrEmpty(Plugin)) return; + _onClicked.Invoke(new PluginViewModel(_notificationService, Plugin, _document, _tab)); + } + } +} diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsNavigationViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointTabItemContentViewModel.cs similarity index 61% rename from Needlework.Net/ViewModels/Pages/Endpoints/EndpointsNavigationViewModel.cs rename to Needlework.Net/ViewModels/Pages/Endpoints/EndpointTabItemContentViewModel.cs index e1b326d..9ddff66 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsNavigationViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointTabItemContentViewModel.cs @@ -1,31 +1,36 @@ -using Avalonia.Collections; -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.Logging; -using Needlework.Net.ViewModels.Shared; using System; +using System.Collections.ObjectModel; namespace Needlework.Net.ViewModels.Pages.Endpoints; -public partial class EndpointsNavigationViewModel : ObservableObject +public partial class EndpointTabItemContentViewModel : ObservableObject { - public Guid Guid { get; } = Guid.NewGuid(); - - [ObservableProperty] private ObservableObject _activeViewModel; - [ObservableProperty] private ObservableObject _endpointsViewModel; - [ObservableProperty] private string _title; - private readonly Action _onEndpointNavigation; + private readonly Tab _tab; - public EndpointsNavigationViewModel(IAvaloniaList plugins, Action onEndpointNavigation, ILogger requestViewModelLogger, Models.Document document, Tab tab) + public EndpointTabItemContentViewModel(Services.NotificationService notificationService, ObservableCollection plugins, Action onEndpointNavigation, IAsyncRelayCommand addEndpointCommand, Models.Document document, Tab tab) { - _activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked, requestViewModelLogger, document, tab); + _activeViewModel = _endpointsViewModel = new EndpointListViewModel(notificationService, new ObservableCollection(plugins), OnClicked, document, tab); _onEndpointNavigation = onEndpointNavigation; _tab = tab; _title = GetTitle(tab); + + AddEndpointCommand = addEndpointCommand; } + public Guid Guid { get; } = Guid.NewGuid(); + + public IAsyncRelayCommand AddEndpointCommand { get; } + + [ObservableProperty] private ObservableObject _activeViewModel; + + [ObservableProperty] private ObservableObject _endpointsViewModel; + + [ObservableProperty] private string _title; + private string GetTitle(Tab tab) { return tab switch @@ -39,7 +44,7 @@ public partial class EndpointsNavigationViewModel : ObservableObject private void OnClicked(ObservableObject viewModel) { ActiveViewModel = viewModel; - if (viewModel is EndpointViewModel endpoint) + if (viewModel is PluginViewModel endpoint) { Title = $"{GetTitle(_tab)} - {endpoint.Title}"; _onEndpointNavigation.Invoke(endpoint.Title, Guid); diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointTabItemViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointTabItemViewModel.cs new file mode 100644 index 0000000..7de7c85 --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointTabItemViewModel.cs @@ -0,0 +1,12 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using FluentAvalonia.UI.Controls; + +namespace Needlework.Net.ViewModels.Pages.Endpoints; + +public partial class EndpointTabItemViewModel : ObservableObject +{ + [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 bool Selected { get; set; } = false; + public required EndpointTabItemContentViewModel Content { get; init; } +} \ No newline at end of file diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointViewModel.cs deleted file mode 100644 index c641347..0000000 --- a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointViewModel.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Avalonia.Collections; -using CommunityToolkit.Mvvm.ComponentModel; -using Microsoft.Extensions.Logging; -using Needlework.Net.ViewModels.Shared; -using System; -using System.Linq; - -namespace Needlework.Net.ViewModels.Pages.Endpoints; - -public partial class EndpointViewModel : ObservableObject -{ - public string Endpoint { get; } - public string Title => Endpoint; - - - public IAvaloniaReadOnlyList PathOperations { get; } - [ObservableProperty] private PathOperationViewModel? _selectedPathOperation; - - [ObservableProperty] private string? _search; - public IAvaloniaList FilteredPathOperations { get; } - - public event EventHandler? PathOperationSelected; - - public EndpointViewModel(string endpoint, ILogger requestViewModelLogger, Models.Document document, Tab tab) - { - Endpoint = endpoint; - PathOperations = new AvaloniaList(document.Plugins[endpoint].Select(x => new PathOperationViewModel(x, requestViewModelLogger, document, tab))); - FilteredPathOperations = new AvaloniaList(PathOperations); - } - - partial void OnSearchChanged(string? value) - { - FilteredPathOperations.Clear(); - - if (string.IsNullOrWhiteSpace(value)) - { - FilteredPathOperations.AddRange(PathOperations); - return; - } - FilteredPathOperations.AddRange(PathOperations.Where(o => o.Path.Contains(value, StringComparison.InvariantCultureIgnoreCase))); - } - - partial void OnSelectedPathOperationChanged(PathOperationViewModel? value) - { - if (value == null) return; - PathOperationSelected?.Invoke(this, value.Operation.RequestTemplate ?? string.Empty); - } -} diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsTabViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsTabViewModel.cs deleted file mode 100644 index 79093f7..0000000 --- a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsTabViewModel.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Avalonia.Collections; -using Avalonia.Threading; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using FluentAvalonia.UI.Controls; -using Microsoft.Extensions.Logging; -using Needlework.Net.Models; -using Needlework.Net.ViewModels.Shared; -using System; -using System.Threading.Tasks; - -namespace Needlework.Net.ViewModels.Pages.Endpoints; - -public enum Tab -{ - LCU, - GameClient -} - -public partial class EndpointsTabViewModel : PageBase -{ - public IAvaloniaList Plugins { get; } = new AvaloniaList(); - public IAvaloniaList Endpoints { get; } = new AvaloniaList(); - - [ObservableProperty] private bool _isBusy = true; - - private readonly ILogger _requestViewModelLogger; - private readonly DataSource _dataSource; - - public EndpointsTabViewModel(ILogger requestViewModelLogger, DataSource dataSource) : base("Endpoints", "list-alt", -500) - { - _requestViewModelLogger = requestViewModelLogger; - _dataSource = dataSource; - } - public override async Task InitializeAsync() - { - await Dispatcher.UIThread.Invoke(async () => await AddEndpoint(Tab.LCU)); - IsBusy = false; - IsInitialized = true; - } - - [RelayCommand] - 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); - Endpoints.Add(new() - { - Content = vm, - Header = vm.Title, - Selected = true - }); - } - - private void OnEndpointNavigation(string? title, Guid guid) - { - foreach (var endpoint in Endpoints) - { - if (endpoint.Content.Guid.Equals(guid)) - { - endpoint.Header = endpoint.Content.Title; - break; - } - } - } -} - -public partial class EndpointItem : ObservableObject -{ - [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 bool Selected { get; set; } = false; - public required EndpointsNavigationViewModel Content { get; init; } -} \ No newline at end of file diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsViewModel.cs index 8a921b1..7942849 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsViewModel.cs @@ -1,52 +1,78 @@ -using Avalonia.Collections; +using Avalonia.Threading; +using AvaloniaEdit.Utils; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.Logging; using Needlework.Net.Models; -using Needlework.Net.ViewModels.Shared; +using Needlework.Net.Services; using System; -using System.Linq; +using System.Collections.ObjectModel; +using System.Threading.Tasks; namespace Needlework.Net.ViewModels.Pages.Endpoints; -public partial class EndpointsViewModel : ObservableObject +public enum Tab { - public IAvaloniaList Plugins { get; } - public IAvaloniaList Query { get; } + LCU, + GameClient +} - [ObservableProperty] private string _search = string.Empty; - [ObservableProperty] private string? _selectedQuery = string.Empty; +public partial class EndpointsViewModel : PageBase +{ + private readonly DocumentService _documentService; - public Action OnClicked { get; } + private readonly NotificationService _notificationService; - private readonly ILogger _requestViewModelLogger; - private readonly Document _document; - private readonly Tab _tab; - - public EndpointsViewModel(IAvaloniaList plugins, Action onClicked, ILogger requestViewModelLogger, Models.Document document, Tab tab) + public EndpointsViewModel(DocumentService documentService, NotificationService notificationService) : base("Endpoints", "list-alt", -500) { - Plugins = new AvaloniaList(plugins); - Query = new AvaloniaList(plugins); - OnClicked = onClicked; - _requestViewModelLogger = requestViewModelLogger; - _document = document; - _tab = tab; + _documentService = documentService; + _notificationService = notificationService; } - partial void OnSearchChanged(string value) + public ObservableCollection Plugins { get; } = []; + + public ObservableCollection Endpoints { get; } = []; + + [ObservableProperty] private bool _isBusy = true; + + public override async Task InitializeAsync() { - Query.Clear(); - if (!string.IsNullOrEmpty(Search)) - Query.AddRange(Plugins.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase))); - else - Query.AddRange(Plugins); + await AddEndpoint(Tab.LCU); + IsBusy = false; } [RelayCommand] - private void OpenEndpoint(string? value) + private async Task AddEndpoint(Tab tab) { - if (string.IsNullOrEmpty(value)) return; + Document document = tab switch + { + Tab.LCU => await _documentService.GetLcuSchemaDocumentAsync(), + Tab.GameClient => await _documentService.GetLolClientDocumentAsync(), + _ => throw new NotImplementedException(), + }; - OnClicked.Invoke(new EndpointViewModel(value, _requestViewModelLogger, _document, _tab)); + await Dispatcher.UIThread.InvokeAsync(() => + { + Plugins.Clear(); + Plugins.AddRange(document.Plugins.Keys); + var vm = new EndpointTabItemContentViewModel(_notificationService, Plugins, OnEndpointNavigation, AddEndpointCommand, document, tab); + Endpoints.Add(new() + { + Content = vm, + Header = vm.Title, + Selected = true + }); + }); + } + + private void OnEndpointNavigation(string? title, Guid guid) + { + foreach (var endpoint in Endpoints) + { + if (endpoint.Content.Guid.Equals(guid)) + { + endpoint.Header = endpoint.Content.Title; + break; + } + } } } diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs index e45be2f..09109c6 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs @@ -1,5 +1,4 @@ -using Avalonia.Collections; -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.OpenApi.Models; using Needlework.Net.Models; using System.Collections.Generic; @@ -11,17 +10,6 @@ namespace Needlework.Net.ViewModels.Pages.Endpoints; public partial class OperationViewModel : ObservableObject { - public string Summary { get; } - public string Description { get; } - public string ReturnType { get; } - public bool IsRequestBody { get; } - public string? RequestBodyType { get; } - public IAvaloniaReadOnlyList RequestClasses { get; } - public IAvaloniaReadOnlyList ResponseClasses { get; } - public IAvaloniaReadOnlyList PathParameters { get; } - public IAvaloniaReadOnlyList QueryParameters { get; } - public string? RequestTemplate { get; } - public OperationViewModel(OpenApiOperation operation, Models.Document document) { Summary = operation.Summary ?? string.Empty; @@ -30,12 +18,32 @@ public partial class OperationViewModel : ObservableObject ReturnType = GetReturnType(operation.Responses); RequestClasses = GetRequestClasses(operation.RequestBody, document); ResponseClasses = GetResponseClasses(operation.Responses, document); - PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path); - QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query); + PathParameters = GetParameters(operation.Parameters.ToList(), ParameterLocation.Path); + QueryParameters = GetParameters(operation.Parameters.ToList(), ParameterLocation.Query); RequestBodyType = GetRequestBodyType(operation.RequestBody); RequestTemplate = GetRequestTemplate(operation.RequestBody, document); } + public string Summary { get; } + + public string Description { get; } + + public string ReturnType { get; } + + public bool IsRequestBody { get; } + + public string? RequestBodyType { get; } + + public List RequestClasses { get; } + + public List ResponseClasses { get; } + + public List PathParameters { get; } + + public List QueryParameters { get; } + + public string? RequestTemplate { get; } + private string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document) { var requestClasses = GetRequestClasses(requestBody, document); @@ -50,7 +58,7 @@ public partial class OperationViewModel : ObservableObject return JsonSerializer.Serialize(JsonSerializer.Deserialize(string.Join(string.Empty, template)), App.JsonSerializerOptions); } - private List CreateTemplate(AvaloniaList requestClasses) + private List CreateTemplate(List requestClasses) { if (requestClasses.Count == 0) return []; List template = []; @@ -83,7 +91,7 @@ public partial class OperationViewModel : ObservableObject } else { - AvaloniaList classes = [.. requestClasses]; + List classes = [.. requestClasses]; classes.Remove(rootClass); template[i] = string.Join(string.Empty, CreateTemplate(classes)); } @@ -121,9 +129,9 @@ public partial class OperationViewModel : ObservableObject return null; } - private AvaloniaList GetParameters(IList parameters, ParameterLocation location) + private List GetParameters(List parameters, ParameterLocation location) { - var pathParameters = new AvaloniaList(); + var pathParameters = new List(); foreach (var parameter in parameters) { if (parameter.In != location) continue; @@ -151,7 +159,7 @@ public partial class OperationViewModel : ObservableObject } - private AvaloniaList GetResponseClasses(OpenApiResponses responses, Document document) + private List GetResponseClasses(OpenApiResponses responses, Document document) { if (!TryGetResponse(responses, out var response)) return []; @@ -162,7 +170,7 @@ public partial class OperationViewModel : ObservableObject var schema = media.Schema; if (schema == null) return []; - AvaloniaList propertyClasses = []; + List propertyClasses = []; WalkSchema(schema, propertyClasses, rawDocument); return propertyClasses; } @@ -170,7 +178,7 @@ public partial class OperationViewModel : ObservableObject return []; } - private void WalkSchema(OpenApiSchema schema, AvaloniaList propertyClasses, OpenApiDocument document) + private void WalkSchema(OpenApiSchema schema, List propertyClasses, OpenApiDocument document) { var type = GetSchemaType(schema); if (IsComponent(type)) @@ -209,7 +217,7 @@ public partial class OperationViewModel : ObservableObject || type.Contains("number")); } - private AvaloniaList GetRequestClasses(OpenApiRequestBody? requestBody, Document document) + private List GetRequestClasses(OpenApiRequestBody? requestBody, Document document) { if (requestBody == null) return []; if (requestBody.Content.TryGetValue("application/json", out var media)) @@ -223,7 +231,7 @@ public partial class OperationViewModel : ObservableObject { var componentId = GetComponentId(schema); var componentSchema = rawDocument.Components.Schemas[componentId]; - AvaloniaList propertyClasses = []; + List propertyClasses = []; WalkSchema(componentSchema, propertyClasses, rawDocument); return propertyClasses; } diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/ParameterViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/ParameterViewModel.cs index d72b027..82c8ed0 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/ParameterViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/ParameterViewModel.cs @@ -4,11 +4,6 @@ namespace Needlework.Net.ViewModels.Pages.Endpoints; public partial class ParameterViewModel : ObservableObject { - public string Name { get; } - public string Type { get; } - public bool IsRequired { get; } - [ObservableProperty] private string? _value = null; - public ParameterViewModel(string name, string type, bool isRequired, string? value = null) { Name = name; @@ -16,4 +11,14 @@ public partial class ParameterViewModel : ObservableObject IsRequired = isRequired; Value = value; } + + public string Name { get; } + + public string Type { get; } + + public bool IsRequired { get; } + + [ObservableProperty] + private string? _value = null; + } diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/PathOperationViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/PathOperationViewModel.cs index d7acaff..e92f467 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/PathOperationViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/PathOperationViewModel.cs @@ -1,6 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.Logging; using Needlework.Net.Models; using Needlework.Net.ViewModels.Shared; using System; @@ -11,27 +10,31 @@ namespace Needlework.Net.ViewModels.Pages.Endpoints; public partial class PathOperationViewModel : ObservableObject { - public string Path { get; } - public OperationViewModel Operation { get; } - - public string Url { get; } - public string Markdown { get; } - - [ObservableProperty] private bool _isBusy; - [ObservableProperty] private Lazy _request; - - public PathOperationViewModel(PathOperation pathOperation, ILogger requestViewModelLogger, Document document, Tab tab) + public PathOperationViewModel(Services.NotificationService notificationService, PathOperation pathOperation, Document document, Tab tab) { Path = pathOperation.Path; Operation = new OperationViewModel(pathOperation.Operation, document); - Request = new(() => new RequestViewModel(requestViewModelLogger, tab) + Request = new(() => new RequestViewModel(notificationService, tab) { - Method = pathOperation.Method.ToUpper() + Method = pathOperation.Method.ToUpper(), + RequestDocument = new(Operation.RequestTemplate ?? string.Empty) }); Url = $"https://swagger.dysolix.dev/lcu/#/{Uri.EscapeDataString(pathOperation.Tag)}/{pathOperation.Operation.OperationId}"; Markdown = $"[{pathOperation.Method.ToUpper()} {Path}]({Url})"; } + public string Path { get; } + + public OperationViewModel Operation { get; } + + public string Url { get; } + + public string Markdown { get; } + + [ObservableProperty] private bool _isBusy; + + [ObservableProperty] private Lazy _request; + [RelayCommand] private async Task SendRequest() { diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/PluginViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/PluginViewModel.cs new file mode 100644 index 0000000..f60fc76 --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/Endpoints/PluginViewModel.cs @@ -0,0 +1,45 @@ +using AvaloniaEdit.Utils; +using CommunityToolkit.Mvvm.ComponentModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Needlework.Net.ViewModels.Pages.Endpoints; + +public partial class PluginViewModel : ObservableObject +{ + public PluginViewModel(Services.NotificationService notificationService, string endpoint, Models.Document document, Tab tab) + { + Endpoint = endpoint; + PathOperations = document.Plugins[endpoint].Select(x => new PathOperationViewModel(notificationService, x, document, tab)).ToList(); + FilteredPathOperations = new ObservableCollection(PathOperations); + } + + public string Endpoint { get; } + + public string Title => Endpoint; + + public List PathOperations { get; } + + [ObservableProperty] + private ObservableCollection _filteredPathOperations; + + [ObservableProperty] + private PathOperationViewModel? _selectedPathOperation; + + [ObservableProperty] + private string? _search; + + partial void OnSearchChanged(string? value) + { + FilteredPathOperations.Clear(); + + if (string.IsNullOrWhiteSpace(value)) + { + FilteredPathOperations.AddRange(PathOperations); + return; + } + FilteredPathOperations.AddRange(PathOperations.Where(o => o.Path.Contains(value, StringComparison.InvariantCultureIgnoreCase))); + } +} diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/PropertyClassViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/PropertyClassViewModel.cs index 9bdfc1f..47b4941 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/PropertyClassViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/PropertyClassViewModel.cs @@ -1,5 +1,4 @@ -using Avalonia.Collections; -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using System.Collections.Generic; @@ -9,14 +8,10 @@ namespace Needlework.Net.ViewModels.Pages.Endpoints; public class PropertyClassViewModel : ObservableObject { - public string Id { get; } - public IAvaloniaReadOnlyList PropertyFields { get; } = new AvaloniaList(); - public IAvaloniaReadOnlyList PropertyEnums { get; } = new AvaloniaList(); - public PropertyClassViewModel(string id, IDictionary properties, IList enumValue) { - AvaloniaList propertyFields = []; - AvaloniaList propertyEnums = []; + List propertyFields = []; + List propertyEnums = []; foreach ((var propertyName, var propertySchema) in properties) { var type = OperationViewModel.GetSchemaType(propertySchema); @@ -32,4 +27,10 @@ public class PropertyClassViewModel : ObservableObject PropertyEnums = propertyEnums; Id = id; } + + public string Id { get; } + + public List PropertyFields { get; } = []; + + public List PropertyEnums { get; } = []; } \ No newline at end of file diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/PropertyEnumViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/PropertyEnumViewModel.cs index 5171f53..b404ce3 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/PropertyEnumViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/PropertyEnumViewModel.cs @@ -6,11 +6,11 @@ namespace Needlework.Net.ViewModels.Pages.Endpoints; public class PropertyEnumViewModel { - public string Type { get; } = "Enum"; - public string Values { get; } - public PropertyEnumViewModel(IList enumValue) { Values = $"[{string.Join(", ", enumValue.Select(x => $"\"{((OpenApiString)x).Value}\"").ToList())}]"; } + public string Type { get; } = "Enum"; + + public string Values { get; } } \ No newline at end of file diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/PropertyFieldViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/PropertyFieldViewModel.cs index 92bf4fa..59870ab 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/PropertyFieldViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/PropertyFieldViewModel.cs @@ -2,12 +2,13 @@ public class PropertyFieldViewModel { - public string Name { get; } - public string Type { get; } - public PropertyFieldViewModel(string name, string type) { Name = name; Type = type; } + + public string Name { get; } + + public string Type { get; } } \ No newline at end of file diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/ResponseViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/ResponseViewModel.cs index 56b5ad2..ad5e794 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/ResponseViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/ResponseViewModel.cs @@ -5,13 +5,6 @@ namespace Needlework.Net.ViewModels.Pages.Endpoints; public partial class ResponseViewModel : ObservableObject { - [ObservableProperty] private string? _path; - [ObservableProperty] private string? _status; - [ObservableProperty] private string? _authentication; - [ObservableProperty] private string? _username; - [ObservableProperty] private string? _password; - [ObservableProperty] private string? _authorization; - public ResponseViewModel(string path) { Path = path; @@ -26,6 +19,24 @@ public partial class ResponseViewModel : ObservableObject } } + [ObservableProperty] + private string? _path; + + [ObservableProperty] + private string? _status; + + [ObservableProperty] + private string? _authentication; + + [ObservableProperty] + private string? _username; + + [ObservableProperty] + private string? _password; + + [ObservableProperty] + private string? _authorization; + private static ProcessInfo? GetProcessInfo() { if (ProcessFinder.IsActive()) return ProcessFinder.GetProcessInfo(); diff --git a/Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs b/Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs new file mode 100644 index 0000000..072feb6 --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs @@ -0,0 +1,35 @@ +using Avalonia.Platform; +using CommunityToolkit.Mvvm.Input; +using Needlework.Net.Models; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Needlework.Net.ViewModels.Pages.Home; + +public partial class HomeViewModel : PageBase +{ + public HomeViewModel() : base("Home", "home", int.MinValue) { } + + public List Libraries { get; } = JsonSerializer.Deserialize>(AssetLoader.Open(new Uri($"avares://NeedleworkDotNet/Assets/libraries.json"))) + !.Where(library => library.Tags.Contains("lcu") || library.Tags.Contains("ingame")) + .Select(library => new LibraryViewModel(library)) + .ToList(); + + public override Task InitializeAsync() + { + return Task.CompletedTask; + } + + [RelayCommand] + private void OpenUrl(string? value) + { + if (value == null) return; + var process = new Process() { StartInfo = new ProcessStartInfo(value) { UseShellExecute = true } }; + process.Start(); + } + +} diff --git a/Needlework.Net/ViewModels/Pages/Home/LibraryViewModel.cs b/Needlework.Net/ViewModels/Pages/Home/LibraryViewModel.cs new file mode 100644 index 0000000..65aab6d --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/Home/LibraryViewModel.cs @@ -0,0 +1,24 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Needlework.Net.Models; +using System.Diagnostics; + +namespace Needlework.Net.ViewModels.Pages.Home +{ + public partial class LibraryViewModel : ObservableObject + { + public LibraryViewModel(Library library) + { + Library = library; + } + + public Library Library { get; } + + [RelayCommand] + private void OpenUrl() + { + var process = new Process() { StartInfo = new ProcessStartInfo(Library.Link) { UseShellExecute = true } }; + process.Start(); + } + } +} diff --git a/Needlework.Net/ViewModels/Pages/HomeViewModel.cs b/Needlework.Net/ViewModels/Pages/HomeViewModel.cs deleted file mode 100644 index 756a706..0000000 --- a/Needlework.Net/ViewModels/Pages/HomeViewModel.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Avalonia.Platform; -using CommunityToolkit.Mvvm.Input; -using Needlework.Net.Models; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text.Json; -using System.Threading.Tasks; - -namespace Needlework.Net.ViewModels.Pages; - -public partial class HomeViewModel : PageBase -{ - public List Libraries { get; } = JsonSerializer.Deserialize>(AssetLoader.Open(new Uri($"avares://NeedleworkDotNet/Assets/libraries.json")))!; - - public HomeViewModel() : base("Home", "home", int.MinValue) { } - - public override Task InitializeAsync() - { - IsInitialized = true; - return Task.CompletedTask; - } - - [RelayCommand] - private void OpenUrl(string url) - { - var process = new Process() - { - StartInfo = new ProcessStartInfo(url) { UseShellExecute = true } - }; - process.Start(); - } - -} diff --git a/Needlework.Net/ViewModels/Pages/PageBase.cs b/Needlework.Net/ViewModels/Pages/PageBase.cs index fff5f1d..0d681e9 100644 --- a/Needlework.Net/ViewModels/Pages/PageBase.cs +++ b/Needlework.Net/ViewModels/Pages/PageBase.cs @@ -9,7 +9,6 @@ public abstract partial class PageBase(string displayName, string icon, int inde [ObservableProperty] private string _displayName = displayName; [ObservableProperty] private string _icon = icon; [ObservableProperty] private int _index = index; - [ObservableProperty] private bool _isInitialized; public abstract Task InitializeAsync(); } \ No newline at end of file diff --git a/Needlework.Net/ViewModels/Pages/Websocket/EventViewModel.cs b/Needlework.Net/ViewModels/Pages/Websocket/EventViewModel.cs index bd94649..373f12f 100644 --- a/Needlework.Net/ViewModels/Pages/Websocket/EventViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Websocket/EventViewModel.cs @@ -2,20 +2,22 @@ using CommunityToolkit.Mvvm.ComponentModel; using System; -namespace Needlework.Net.ViewModels.Pages.Websocket; +namespace Needlework.Net.ViewModels.Pages.WebSocket; public class EventViewModel : ObservableObject { - public string Time { get; } - public string Type { get; } - public string Uri { get; } - - public string Key => $"{Time} {Type} {Uri}"; - public EventViewModel(EventData eventData) { Time = $"{DateTime.Now:HH:mm:ss.fff}"; Type = eventData?.EventType?.ToUpper() ?? string.Empty; Uri = eventData?.Uri ?? string.Empty; } + + public string Time { get; } + + public string Type { get; } + + public string Uri { get; } + + public string Key => $"{Time} {Type} {Uri}"; } diff --git a/Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs b/Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs index 982d7e9..08a2390 100644 --- a/Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs @@ -1,11 +1,15 @@ using Avalonia.Collections; +using AvaloniaEdit.Document; using BlossomiShymae.Briar; using BlossomiShymae.Briar.WebSocket.Events; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; -using Microsoft.Extensions.Logging; +using Flurl.Http; +using Flurl.Http.Configuration; +using Needlework.Net.Extensions; using Needlework.Net.Messages; +using Needlework.Net.Services; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -17,67 +21,81 @@ using System.Threading; using System.Threading.Tasks; using Websocket.Client; -namespace Needlework.Net.ViewModels.Pages.Websocket; +namespace Needlework.Net.ViewModels.Pages.WebSocket; -public partial class WebsocketViewModel : PageBase +public partial class WebSocketViewModel : PageBase, IEnableLogger { - public ObservableCollection EventLog { get; } = []; - public SemaphoreSlim EventLogLock { get; } = new(1, 1); - - [NotifyPropertyChangedFor(nameof(FilteredEventLog))] - [ObservableProperty] private string _search = string.Empty; - [ObservableProperty] private bool _isAttach = true; - [ObservableProperty] private bool _isTail = false; - [ObservableProperty] private EventViewModel? _selectedEventLog = null; - - [ObservableProperty] private IAvaloniaList _eventTypes = new AvaloniaList(); - [ObservableProperty] private string _eventType = "OnJsonApiEvent"; - private Dictionary _events = []; + private readonly IFlurlClient _githubUserContentClient; + + private readonly NotificationService _notificationService; + + private readonly object _tokenLock = new(); + + public WebSocketViewModel(IFlurlClientCache clients, NotificationService notificationService) : base("Event Viewer", "plug", -100) + { + _githubUserContentClient = clients.Get("GithubUserContentClient"); + _notificationService = notificationService; + + EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog)); + } + + public ObservableCollection EventLog { get; } = []; + + public SemaphoreSlim EventLogLock { get; } = new(1, 1); + public WebsocketClient? Client { get; set; } public List ClientDisposables = []; - private readonly object _tokenLock = new(); public CancellationTokenSource TokenSource { get; set; } = new(); - public HttpClient HttpClient { get; } - public IReadOnlyList FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))]; - private readonly ILogger _logger; + [NotifyPropertyChangedFor(nameof(FilteredEventLog))] + [ObservableProperty] + private string _search = string.Empty; - public WebsocketViewModel(HttpClient httpClient, ILogger logger) : base("Event Viewer", "plug", -100) - { - _logger = logger; - HttpClient = httpClient; - EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog)); - Task.Run(async () => - { - await InitializeEventTypes(); - InitializeWebsocket(); - }); - } + [ObservableProperty] + private bool _isAttach = true; - public override Task InitializeAsync() + [ObservableProperty] + private bool _isTail = false; + + [ObservableProperty] + private EventViewModel? _selectedEventLog = null; + + [ObservableProperty] + private IAvaloniaList _eventTypes = new AvaloniaList(); + + [ObservableProperty] + private string _eventType = "OnJsonApiEvent"; + + [ObservableProperty] + private TextDocument _document = new(); + + public override async Task InitializeAsync() { - IsInitialized = true; - return Task.CompletedTask; + await InitializeEventTypes(); + InitializeWebsocket(); } private async Task InitializeEventTypes() { try { - var file = await HttpClient.GetStringAsync("https://raw.githubusercontent.com/dysolix/hasagi-types/refs/heads/main/dist/lcu-events.d.ts"); + var file = await _githubUserContentClient.Request("/dysolix/hasagi-types/refs/heads/main/dist/lcu-events.d.ts") + .GetStringAsync(); var matches = EventTypesRegex().Matches(file); Avalonia.Threading.Dispatcher.UIThread.Invoke(() => EventTypes.AddRange(matches.Select(m => m.Groups[1].Value))); } catch (HttpRequestException ex) { - _logger.LogError(ex, "Failed to get event types"); - WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new("Failed to get event types", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(10)))); + var message = "Failed to get event types from GitHub. Please check your internet connection or try again later."; + this.Log() + .Error(ex, message); + _notificationService.Notify("Needlework.Net", message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error); } } @@ -87,7 +105,8 @@ public partial class WebsocketViewModel : PageBase { if (Client != null) { - _logger.LogDebug("Disposing old connection"); + this.Log() + .Debug("Disposing old connection"); foreach (var disposable in ClientDisposables) disposable.Dispose(); ClientDisposables.Clear(); @@ -117,7 +136,8 @@ public partial class WebsocketViewModel : PageBase }) { IsBackground = true }; thread.Start(); - _logger.LogDebug("Initialized new connection: {EventType}", EventType); + this.Log() + .Debug("Initialized new connection: {EventType}", EventType); TokenSource = tokenSource; } } @@ -129,7 +149,7 @@ public partial class WebsocketViewModel : PageBase { var text = JsonSerializer.Serialize(message, App.JsonSerializerOptions); if (text.Length >= App.MaxCharacters) WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(text)); - else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(text), nameof(WebsocketViewModel)); + else Document = new(text); } } @@ -138,16 +158,19 @@ public partial class WebsocketViewModel : PageBase { _events.Clear(); EventLog.Clear(); + Document = new(); } private void OnReconnection(ReconnectionInfo info) { - _logger.LogTrace("Reconnected: {Type}", info.Type); + this.Log() + .Debug("Reconnected: {Type}", info.Type); } private void OnDisconnection(DisconnectionInfo info) { - _logger.LogTrace("Disconnected: {Type}", info.Type); + this.Log() + .Debug("Disconnected: {Type}", info.Type); InitializeWebsocket(); } diff --git a/Needlework.Net/ViewModels/Shared/RequestViewModel.cs b/Needlework.Net/ViewModels/Shared/RequestViewModel.cs index 75e4a2a..3c2647f 100644 --- a/Needlework.Net/ViewModels/Shared/RequestViewModel.cs +++ b/Needlework.Net/ViewModels/Shared/RequestViewModel.cs @@ -1,11 +1,12 @@ using Avalonia.Media; +using AvaloniaEdit.Document; using BlossomiShymae.Briar; using BlossomiShymae.Briar.Utils; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; -using Microsoft.Extensions.Logging; +using Needlework.Net.Extensions; using Needlework.Net.Messages; -using Needlework.Net.ViewModels.MainWindow; +using Needlework.Net.Services; using Needlework.Net.ViewModels.Pages.Endpoints; using System; using System.Net.Http; @@ -14,35 +15,54 @@ using System.Threading.Tasks; namespace Needlework.Net.ViewModels.Shared; -public partial class RequestViewModel : ObservableObject +public partial class RequestViewModel : ObservableObject, IEnableLogger { - [ObservableProperty] private string? _method = "GET"; - [ObservableProperty] private SolidColorBrush _color = new(GetColor("GET")); + private readonly NotificationService _notificationService; - [ObservableProperty] private bool _isRequestBusy = false; - [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; - [ObservableProperty] private string? _responseUsername = null; - [ObservableProperty] private string? _responsePassword = null; - [ObservableProperty] private string? _responseAuthorization = null; - [ObservableProperty] private string? _responseBody = null; - - public event EventHandler? RequestText; - public event EventHandler? UpdateText; - - private readonly ILogger _logger; private readonly Tab _tab; - public RequestViewModel(ILogger logger, Pages.Endpoints.Tab tab) + public RequestViewModel(NotificationService notificationService, Tab tab) { - _logger = logger; _tab = tab; + _notificationService = notificationService; } + [ObservableProperty] + private string? _method = "GET"; + + [ObservableProperty] + private SolidColorBrush _color = new(GetColor("GET")); + + [ObservableProperty] + private bool _isRequestBusy; + + [ObservableProperty] + private string? _requestPath; + + [ObservableProperty] + private TextDocument _requestDocument = new(); + + [ObservableProperty] + private TextDocument _responseDocument = new(); + + [ObservableProperty] + private string? _responsePath; + + [ObservableProperty] + private string? _responseStatus; + + [ObservableProperty] + private string? _responseAuthentication; + + [ObservableProperty] + private string? _responseUsername; + + [ObservableProperty] + private string? _responsePassword; + + [ObservableProperty] + private string? _responseAuthorization; + partial void OnMethodChanged(string? oldValue, string? newValue) { if (newValue == null) return; @@ -74,9 +94,10 @@ public partial class RequestViewModel : ObservableObject throw new Exception("Path is empty."); var method = GetMethod(); - _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")); + this.Log() + .Debug("Sending request: {Tuple}", (Method, RequestPath)); + var requestBody = RequestDocument.Text; + var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json")); var client = Connector.GetGameHttpClientInstance(); var response = await client.SendAsync(new HttpRequestMessage(method, RequestPath) { Content = content }); var responseBody = await response.Content.ReadAsByteArrayAsync(); @@ -85,12 +106,11 @@ public partial class RequestViewModel : ObservableObject if (body.Length > App.MaxCharacters) { WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body)); - UpdateText?.Invoke(this, string.Empty); + ResponseDocument = new(); } else { - ResponseBody = body; - UpdateText?.Invoke(this, body); + ResponseDocument = new(body); } ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}"; @@ -99,9 +119,9 @@ public partial class RequestViewModel : ObservableObject } 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); + this.Log() + .Error(ex, "Request failed: {Tuple}", (Method, RequestPath)); + _notificationService.Notify("Request failed", ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error); ResponseStatus = null; ResponsePath = null; @@ -109,7 +129,7 @@ public partial class RequestViewModel : ObservableObject ResponseAuthorization = null; ResponseUsername = null; ResponsePassword = null; - ResponseBody = null; + ResponseDocument = new(); } finally { @@ -126,11 +146,12 @@ public partial class RequestViewModel : ObservableObject throw new Exception("Path is empty."); var method = GetMethod(); - _logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath)); + this.Log() + .Debug("Sending request: {Tuple}", (Method, RequestPath)); var processInfo = ProcessFinder.GetProcessInfo(); - RequestText?.Invoke(this, this); - var content = new StringContent(RequestBody ?? string.Empty, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json")); + var requestBody = RequestDocument.Text; + var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json")); var client = Connector.GetLcuHttpClientInstance(); var response = await client.SendAsync(new(method, RequestPath) { Content = content }); var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken); @@ -140,12 +161,11 @@ public partial class RequestViewModel : ObservableObject if (body.Length >= App.MaxCharacters) { WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body)); - UpdateText?.Invoke(this, string.Empty); + ResponseDocument = new(); } else { - ResponseBody = body; - UpdateText?.Invoke(this, body); + ResponseDocument = new(body); } ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}"; @@ -157,9 +177,9 @@ public partial class RequestViewModel : ObservableObject } 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); + this.Log() + .Error(ex, "Request failed: {Tuple}", (Method, RequestPath)); + _notificationService.Notify("Request failed", ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error); ResponseStatus = null; ResponsePath = null; @@ -167,7 +187,7 @@ public partial class RequestViewModel : ObservableObject ResponseAuthorization = null; ResponseUsername = null; ResponsePassword = null; - ResponseBody = null; + ResponseDocument = new(); } finally { diff --git a/Needlework.Net/Views/MainWindow/MainWindowView.axaml b/Needlework.Net/Views/MainWindow/MainWindowView.axaml index 812a6c7..53849b5 100644 --- a/Needlework.Net/Views/MainWindow/MainWindowView.axaml +++ b/Needlework.Net/Views/MainWindow/MainWindowView.axaml @@ -28,11 +28,10 @@ DockPanel.Dock="Left" Grid.Column="0"/> - Needlework.Net - + Grid.Column="1"/> + MenuItemsSource="{Binding NavigationViewItems}" + SelectedItem="{Binding SelectedNavigationViewItem}"> @@ -75,27 +74,14 @@ - + + + + diff --git a/Needlework.Net/Views/MainWindow/NotificationView.axaml.cs b/Needlework.Net/Views/MainWindow/NotificationView.axaml.cs new file mode 100644 index 0000000..5a6a035 --- /dev/null +++ b/Needlework.Net/Views/MainWindow/NotificationView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Views.MainWindow; + +public partial class NotificationView : UserControl +{ + public NotificationView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Needlework.Net/Views/MainWindow/OopsiesDialog.cs b/Needlework.Net/Views/MainWindow/OopsiesDialog.cs index b2b306d..1574b20 100644 --- a/Needlework.Net/Views/MainWindow/OopsiesDialog.cs +++ b/Needlework.Net/Views/MainWindow/OopsiesDialog.cs @@ -10,8 +10,10 @@ namespace Needlework.Net.Views.MainWindow; public class OopsiesDialog : IDialog, IDisposable { private bool _isDisposing; + private string? _text; - private ContentDialog _dialog; + + private readonly ContentDialog _dialog; public OopsiesDialog() { diff --git a/Needlework.Net/Views/Pages/AboutView.axaml b/Needlework.Net/Views/Pages/About/AboutView.axaml similarity index 94% rename from Needlework.Net/Views/Pages/AboutView.axaml rename to Needlework.Net/Views/Pages/About/AboutView.axaml index 3323f3b..23e1bcc 100644 --- a/Needlework.Net/Views/Pages/AboutView.axaml +++ b/Needlework.Net/Views/Pages/About/AboutView.axaml @@ -2,11 +2,11 @@ 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:vm="using:Needlework.Net.ViewModels.Pages" + xmlns:vm="using:Needlework.Net.ViewModels.Pages.About" xmlns:controls="using:Needlework.Net.Controls" xmlns:i="https://github.com/projektanker/icons.avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Needlework.Net.Views.Pages.AboutView" + x:Class="Needlework.Net.Views.Pages.About.AboutView" x:DataType="vm:AboutViewModel"> diff --git a/Needlework.Net/Views/Pages/AboutView.axaml.cs b/Needlework.Net/Views/Pages/About/AboutView.axaml.cs similarity index 76% rename from Needlework.Net/Views/Pages/AboutView.axaml.cs rename to Needlework.Net/Views/Pages/About/AboutView.axaml.cs index d70d817..5f3f8a4 100644 --- a/Needlework.Net/Views/Pages/AboutView.axaml.cs +++ b/Needlework.Net/Views/Pages/About/AboutView.axaml.cs @@ -1,6 +1,6 @@ using Avalonia.Controls; -namespace Needlework.Net.Views.Pages; +namespace Needlework.Net.Views.Pages.About; public partial class AboutView : UserControl { diff --git a/Needlework.Net/Views/Pages/ConsoleView.axaml b/Needlework.Net/Views/Pages/Console/ConsoleView.axaml similarity index 92% rename from Needlework.Net/Views/Pages/ConsoleView.axaml rename to Needlework.Net/Views/Pages/Console/ConsoleView.axaml index 0d60668..bd91454 100644 --- a/Needlework.Net/Views/Pages/ConsoleView.axaml +++ b/Needlework.Net/Views/Pages/Console/ConsoleView.axaml @@ -5,10 +5,10 @@ xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit" xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives" - xmlns:vm="using:Needlework.Net.ViewModels.Pages" + xmlns:vm="using:Needlework.Net.ViewModels.Pages.Console" xmlns:controls="using:Needlework.Net.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Needlework.Net.Views.Pages.ConsoleView" + x:Class="Needlework.Net.Views.Pages.Console.ConsoleView" x:DataType="vm:ConsoleViewModel"> @@ -52,7 +52,7 @@ Text="{Binding Request.ResponsePath}"/> ("ResponseEditor"); - _requestEditor = this.FindControl("RequestEditor"); - _responseEditor?.ApplyJsonEditorSettings(); - _requestEditor?.ApplyJsonEditorSettings(); - - var vm = (ConsoleViewModel)DataContext!; - vm.Request.RequestText += LcuRequest_RequestText; ; - vm.Request.UpdateText += LcuRequest_UpdateText; - - OnBaseThemeChanged(Application.Current!.ActualThemeVariant); - } - - private void LcuRequest_RequestText(object? sender, ViewModels.Shared.RequestViewModel e) - { - e.RequestBody = _requestEditor!.Text; - } - - private void LcuRequest_UpdateText(object? sender, string e) - { - _responseEditor!.Text = e; - } - - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - - var vm = (ConsoleViewModel)DataContext!; - vm.Request.RequestText -= LcuRequest_RequestText; - vm.Request.UpdateText -= LcuRequest_UpdateText; - } - - private void OnBaseThemeChanged(ThemeVariant currentTheme) - { - var registryOptions = new RegistryOptions( - currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus); - } -} \ No newline at end of file diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointListView.axaml b/Needlework.Net/Views/Pages/Endpoints/EndpointListView.axaml new file mode 100644 index 0000000..bb9a3ab --- /dev/null +++ b/Needlework.Net/Views/Pages/Endpoints/EndpointListView.axaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointListView.axaml.cs b/Needlework.Net/Views/Pages/Endpoints/EndpointListView.axaml.cs new file mode 100644 index 0000000..e39e046 --- /dev/null +++ b/Needlework.Net/Views/Pages/Endpoints/EndpointListView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Views.Pages.Endpoints; + +public partial class EndpointListView : UserControl +{ + public EndpointListView() + { + InitializeComponent(); + } +} diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointSearchDetailsView.axaml b/Needlework.Net/Views/Pages/Endpoints/EndpointSearchDetailsView.axaml new file mode 100644 index 0000000..b348e81 --- /dev/null +++ b/Needlework.Net/Views/Pages/Endpoints/EndpointSearchDetailsView.axaml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointTabItemContentView.axaml.cs b/Needlework.Net/Views/Pages/Endpoints/EndpointTabItemContentView.axaml.cs new file mode 100644 index 0000000..2f0cec2 --- /dev/null +++ b/Needlework.Net/Views/Pages/Endpoints/EndpointTabItemContentView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Views.Pages.Endpoints; + +public partial class EndpointTabItemContentView : UserControl +{ + public EndpointTabItemContentView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointView.axaml.cs b/Needlework.Net/Views/Pages/Endpoints/EndpointView.axaml.cs deleted file mode 100644 index 7d6db77..0000000 --- a/Needlework.Net/Views/Pages/Endpoints/EndpointView.axaml.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Styling; -using AvaloniaEdit; -using Needlework.Net.Extensions; -using Needlework.Net.ViewModels.Pages.Endpoints; -using Needlework.Net.ViewModels.Shared; -using TextMateSharp.Grammars; - -namespace Needlework.Net.Views.Pages.Endpoints; - -public partial class EndpointView : UserControl -{ - private TextEditor? _requestEditor; - private TextEditor? _responseEditor; - private RequestViewModel? _lcuRequestVm; - - public EndpointView() - { - InitializeComponent(); - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - _requestEditor = this.FindControl("EndpointRequestEditor"); - _responseEditor = this.FindControl("EndpointResponseEditor"); - _requestEditor?.ApplyJsonEditorSettings(); - _responseEditor?.ApplyJsonEditorSettings(); - - var vm = (EndpointViewModel)DataContext!; - vm.PathOperationSelected += Vm_PathOperationSelected; - - if (vm.SelectedPathOperation != null) - { - _lcuRequestVm = vm.SelectedPathOperation.Request.Value; - vm.SelectedPathOperation.Request.Value.RequestText += LcuRequest_RequestText; - vm.SelectedPathOperation.Request.Value.UpdateText += LcuRequest_UpdateText; - } - - OnBaseThemeChanged(Application.Current!.ActualThemeVariant); - } - - private void Vm_PathOperationSelected(object? sender, string e) - { - var vm = (EndpointViewModel)DataContext!; - if (vm.SelectedPathOperation != null) - { - _requestEditor!.Text = e; - if (_lcuRequestVm != null) - { - _lcuRequestVm.RequestText -= LcuRequest_RequestText; - _lcuRequestVm.UpdateText -= LcuRequest_UpdateText; - } - vm.SelectedPathOperation.Request.Value.RequestText += LcuRequest_RequestText; - vm.SelectedPathOperation.Request.Value.UpdateText += LcuRequest_UpdateText; - _lcuRequestVm = vm.SelectedPathOperation.Request.Value; - _responseEditor!.Text = vm.SelectedPathOperation.Request.Value.ResponseBody ?? string.Empty; - } - } - - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - - var vm = (EndpointViewModel)DataContext!; - vm.PathOperationSelected -= Vm_PathOperationSelected; - - if (_lcuRequestVm != null) - { - _lcuRequestVm.RequestText -= LcuRequest_RequestText; - _lcuRequestVm.UpdateText -= LcuRequest_UpdateText; - _lcuRequestVm = null; - } - } - - private void OnBaseThemeChanged(ThemeVariant currentTheme) - { - var registryOptions = new RegistryOptions( - currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus); - } - - private void LcuRequest_RequestText(object? sender, RequestViewModel e) - { - e.RequestBody = _requestEditor!.Text; - } - - private void LcuRequest_UpdateText(object? sender, string e) - { - _responseEditor!.Text = e; - } - -} \ No newline at end of file diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointsNavigationView.axaml b/Needlework.Net/Views/Pages/Endpoints/EndpointsNavigationView.axaml deleted file mode 100644 index e3eed3c..0000000 --- a/Needlework.Net/Views/Pages/Endpoints/EndpointsNavigationView.axaml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointsTabView.axaml b/Needlework.Net/Views/Pages/Endpoints/EndpointsTabView.axaml deleted file mode 100644 index 90d8bbb..0000000 --- a/Needlework.Net/Views/Pages/Endpoints/EndpointsTabView.axaml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointsTabView.axaml.cs b/Needlework.Net/Views/Pages/Endpoints/EndpointsTabView.axaml.cs deleted file mode 100644 index 980961a..0000000 --- a/Needlework.Net/Views/Pages/Endpoints/EndpointsTabView.axaml.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Avalonia.Controls; -using Needlework.Net.ViewModels.Pages.Endpoints; -using System.Collections; - -namespace Needlework.Net.Views.Pages.Endpoints; - -public partial class EndpointsTabView : UserControl -{ - public EndpointsTabView() - { - InitializeComponent(); - } - - private void TabView_TabCloseRequested(FluentAvalonia.UI.Controls.TabView sender, FluentAvalonia.UI.Controls.TabViewTabCloseRequestedEventArgs args) - { - if (args.Tab.Content is EndpointItem item && sender.TabItems is IList tabItems) - { - if (tabItems.Count > 1) - { - tabItems.Remove(item); - } - } - } -} \ No newline at end of file diff --git a/Needlework.Net/Views/Pages/Endpoints/EndpointsView.axaml b/Needlework.Net/Views/Pages/Endpoints/EndpointsView.axaml index e0f6a12..d9b203c 100644 --- a/Needlework.Net/Views/Pages/Endpoints/EndpointsView.axaml +++ b/Needlework.Net/Views/Pages/Endpoints/EndpointsView.axaml @@ -1,29 +1,58 @@ - - - - - - - - @@ -99,26 +101,9 @@ HorizontalScrollBarVisibility="Disabled"> - - - - - - - - - - - + + diff --git a/Needlework.Net/Views/Pages/HomeView.axaml.cs b/Needlework.Net/Views/Pages/Home/HomeView.axaml.cs similarity index 76% rename from Needlework.Net/Views/Pages/HomeView.axaml.cs rename to Needlework.Net/Views/Pages/Home/HomeView.axaml.cs index b05c02a..72c7dcb 100644 --- a/Needlework.Net/Views/Pages/HomeView.axaml.cs +++ b/Needlework.Net/Views/Pages/Home/HomeView.axaml.cs @@ -1,6 +1,6 @@ using Avalonia.Controls; -namespace Needlework.Net.Views.Pages; +namespace Needlework.Net.Views.Pages.Home; public partial class HomeView : UserControl { diff --git a/Needlework.Net/Views/Pages/Home/LibraryView.axaml b/Needlework.Net/Views/Pages/Home/LibraryView.axaml new file mode 100644 index 0000000..ea56362 --- /dev/null +++ b/Needlework.Net/Views/Pages/Home/LibraryView.axaml @@ -0,0 +1,27 @@ + + + + + - + + + + + + diff --git a/Needlework.Net/Views/Pages/Home/LibraryView.axaml.cs b/Needlework.Net/Views/Pages/Home/LibraryView.axaml.cs new file mode 100644 index 0000000..52807d6 --- /dev/null +++ b/Needlework.Net/Views/Pages/Home/LibraryView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Views.Pages.Home; + +public partial class LibraryView : UserControl +{ + public LibraryView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Needlework.Net/Views/Pages/WebSocket/EventView.axaml b/Needlework.Net/Views/Pages/WebSocket/EventView.axaml new file mode 100644 index 0000000..05b9748 --- /dev/null +++ b/Needlework.Net/Views/Pages/WebSocket/EventView.axaml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/Needlework.Net/Views/Pages/WebSocket/EventView.axaml.cs b/Needlework.Net/Views/Pages/WebSocket/EventView.axaml.cs new file mode 100644 index 0000000..a258383 --- /dev/null +++ b/Needlework.Net/Views/Pages/WebSocket/EventView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Views.Pages.WebSocket; + +public partial class EventView : UserControl +{ + public EventView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Needlework.Net/Views/Pages/WebsocketView.axaml b/Needlework.Net/Views/Pages/WebSocket/WebSocketView.axaml similarity index 78% rename from Needlework.Net/Views/Pages/WebsocketView.axaml rename to Needlework.Net/Views/Pages/WebSocket/WebSocketView.axaml index 284ee97..751a3e2 100644 --- a/Needlework.Net/Views/Pages/WebsocketView.axaml +++ b/Needlework.Net/Views/Pages/WebSocket/WebSocketView.axaml @@ -3,10 +3,10 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit" - xmlns:vm="using:Needlework.Net.ViewModels.Pages.Websocket" + xmlns:vm="using:Needlework.Net.ViewModels.Pages.WebSocket" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Needlework.Net.Views.Pages.WebsocketView" - x:DataType="vm:WebsocketViewModel"> + x:Class="Needlework.Net.Views.Pages.WebSocket.WebSocketView" + x:DataType="vm:WebSocketViewModel"> @@ -55,20 +55,8 @@ SelectedItem="{Binding SelectedEventLog}" ScrollViewer.HorizontalScrollBarVisibility="Auto"> - - - - - - + + @@ -79,6 +67,7 @@ Padding="0 8 0 0"> +public partial class WebSocketView : UserControl { - private TextEditor? _responseEditor; - public WebsocketViewModel? _viewModel; + public WebSocketViewModel? _viewModel; private ListBox? _viewer; - public WebsocketView() + public WebSocketView() { InitializeComponent(); - } - public void Receive(ResponseUpdatedMessage message) - { - _responseEditor!.Text = message.Value; + ResponseEditor.ApplyJsonEditorSettings(); + OnBaseThemeChanged(Application.Current!.ActualThemeVariant); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _viewModel = (WebsocketViewModel)DataContext!; + _viewModel = (WebSocketViewModel)DataContext!; _viewer = this.FindControl("EventViewer"); - _viewModel.EventLog.CollectionChanged += EventLog_CollectionChanged; ; - - _responseEditor = this.FindControl("ResponseEditor"); - _responseEditor?.ApplyJsonEditorSettings(); - - WeakReferenceMessenger.Default.Register(this, nameof(WebsocketViewModel)); - - OnBaseThemeChanged(Application.Current!.ActualThemeVariant); + _viewModel.EventLog.CollectionChanged += EventLog_CollectionChanged; } private void EventLog_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) @@ -66,7 +53,6 @@ public partial class WebsocketView : UserControl, IRecipient