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