mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 10:10:48 +01:00
Compare commits
20 Commits
38e1ea2301
...
0.11.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
375d5a2ff8 | ||
|
|
2aa77f3e02 | ||
|
|
576863bd72 | ||
|
|
68e5abd1d1 | ||
|
|
b18f425257 | ||
|
|
5ebed22ae3 | ||
|
|
dc44cf72df | ||
|
|
01cb8886c6 | ||
|
|
38e4a64bb8 | ||
|
|
b63713f054 | ||
|
|
6a776dfd5f | ||
|
|
9270c6d1f1 | ||
|
|
f65c6f1b09 | ||
|
|
bd6589c310 | ||
|
|
cf947f3af4 | ||
|
|
2e4637f533 | ||
|
|
7aaa79956c | ||
|
|
e9d4615ecf | ||
|
|
fb63adc1b7 | ||
|
|
b41be19cd9 |
Binary file not shown.
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 5.1 KiB |
1
Needlework.Net/Assets/libraries.json
Normal file
1
Needlework.Net/Assets/libraries.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[{"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"}]
|
||||||
9
Needlework.Net/Models/Library.cs
Normal file
9
Needlework.Net/Models/Library.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Needlework.Net.Models;
|
||||||
|
|
||||||
|
public class Library
|
||||||
|
{
|
||||||
|
public required string Repo { get; init; }
|
||||||
|
public string? Description { get; init; }
|
||||||
|
public required string Language { get; init; }
|
||||||
|
public required string Link { get; init; }
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
namespace Needlework.Net.Models;
|
namespace Needlework.Net.Models;
|
||||||
@@ -35,10 +36,10 @@ public class OpenApiDocumentWrapper
|
|||||||
{
|
{
|
||||||
pluginsKey = "default";
|
pluginsKey = "default";
|
||||||
if (plugins.TryGetValue(pluginsKey, out var p))
|
if (plugins.TryGetValue(pluginsKey, out var p))
|
||||||
p.Add(new(method.ToString(), path, operation));
|
p.Add(new(method.ToString(), path, pluginsKey, operation));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
operations.Add(new(method.ToString(), path, operation));
|
operations.Add(new(method.ToString(), path, pluginsKey, operation));
|
||||||
plugins[pluginsKey] = operations;
|
plugins[pluginsKey] = operations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,19 +47,16 @@ public class OpenApiDocumentWrapper
|
|||||||
{
|
{
|
||||||
foreach (var tag in operation.Tags)
|
foreach (var tag in operation.Tags)
|
||||||
{
|
{
|
||||||
var lowercaseTag = tag.Name.ToLower();
|
if (tag.Name == "plugins")
|
||||||
if (lowercaseTag == "plugins")
|
|
||||||
continue;
|
continue;
|
||||||
else if (lowercaseTag.Contains("plugin "))
|
|
||||||
pluginsKey = lowercaseTag.Replace("plugin ", "");
|
|
||||||
else
|
else
|
||||||
pluginsKey = lowercaseTag;
|
pluginsKey = tag.Name;
|
||||||
|
|
||||||
if (plugins.TryGetValue(pluginsKey, out var p))
|
if (plugins.TryGetValue(pluginsKey, out var p))
|
||||||
p.Add(new(method.ToString(), path, operation));
|
p.Add(new(method.ToString(), path, tag.Name, operation));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
operations.Add(new(method.ToString(), path, operation));
|
operations.Add(new(method.ToString(), path, tag.Name, operation));
|
||||||
plugins[pluginsKey] = operations;
|
plugins[pluginsKey] = operations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +64,10 @@ public class OpenApiDocumentWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugins = new(plugins.ToDictionary(
|
||||||
|
kvp => kvp.Key,
|
||||||
|
kvp => kvp.Value.OrderBy(x => x.Path).ToList()));
|
||||||
|
|
||||||
Plugins = plugins;
|
Plugins = plugins;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ using Microsoft.OpenApi.Models;
|
|||||||
|
|
||||||
namespace Needlework.Net.Models;
|
namespace Needlework.Net.Models;
|
||||||
|
|
||||||
public record PathOperation(string Method, string Path, OpenApiOperation Operation);
|
public record PathOperation(string Method, string Path, string Tag, OpenApiOperation Operation);
|
||||||
10
Needlework.Net/Models/SystemBuild.cs
Normal file
10
Needlework.Net/Models/SystemBuild.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Needlework.Net.Models
|
||||||
|
{
|
||||||
|
public class SystemBuild
|
||||||
|
{
|
||||||
|
public string Branch { get; set; } = string.Empty;
|
||||||
|
public string Patchline { get; set; } = string.Empty;
|
||||||
|
public string PatchlineVisibleName { get; set; } = string.Empty;
|
||||||
|
public string Version { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
||||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||||
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
||||||
<AssemblyVersion>0.8.0.0</AssemblyVersion>
|
<AssemblyVersion>0.11.0.0</AssemblyVersion>
|
||||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||||
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -31,13 +31,17 @@
|
|||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0" />
|
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0" />
|
||||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
|
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.OpenApi" Version="1.6.22" />
|
<PackageReference Include="Microsoft.OpenApi" Version="1.6.22" />
|
||||||
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.22" />
|
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.22" />
|
||||||
<PackageReference Include="Projektanker.Icons.Avalonia" Version="9.4.0" />
|
<PackageReference Include="Projektanker.Icons.Avalonia" Version="9.4.0" />
|
||||||
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.0" />
|
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.0" />
|
||||||
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.64" />
|
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
|
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.65" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ using Needlework.Net.ViewModels.MainWindow;
|
|||||||
using Needlework.Net.ViewModels.Pages;
|
using Needlework.Net.ViewModels.Pages;
|
||||||
using Projektanker.Icons.Avalonia;
|
using Projektanker.Icons.Avalonia;
|
||||||
using Projektanker.Icons.Avalonia.FontAwesome;
|
using Projektanker.Icons.Avalonia.FontAwesome;
|
||||||
|
using Serilog;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Needlework.Net;
|
namespace Needlework.Net;
|
||||||
|
|
||||||
@@ -34,7 +36,11 @@ class Program
|
|||||||
return AppBuilder.Configure(() => new App(BuildServices()))
|
return AppBuilder.Configure(() => new App(BuildServices()))
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.WithInterFont()
|
.WithInterFont()
|
||||||
.LogToTrace();
|
.LogToTrace()
|
||||||
|
.With(new Win32PlatformOptions
|
||||||
|
{
|
||||||
|
CompositionMode = [ Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition ]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IServiceProvider BuildServices()
|
private static IServiceProvider BuildServices()
|
||||||
@@ -44,9 +50,16 @@ class Program
|
|||||||
builder.AddSingleton<MainWindowViewModel>();
|
builder.AddSingleton<MainWindowViewModel>();
|
||||||
builder.AddSingleton<DialogService>();
|
builder.AddSingleton<DialogService>();
|
||||||
builder.AddSingletonsFromAssemblies<PageBase>();
|
builder.AddSingletonsFromAssemblies<PageBase>();
|
||||||
|
|
||||||
builder.AddHttpClient();
|
builder.AddHttpClient();
|
||||||
|
|
||||||
|
var logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Debug()
|
||||||
|
.WriteTo.File("Logs/NeedleworkDotNet.log", 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.AddLogging(builder => builder.AddSerilog(logger));
|
||||||
|
|
||||||
var services = builder.BuildServiceProvider();
|
var services = builder.BuildServiceProvider();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.Models;
|
using Needlework.Net.Models;
|
||||||
@@ -17,8 +19,10 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Timers;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.MainWindow;
|
namespace Needlework.Net.ViewModels.MainWindow;
|
||||||
|
|
||||||
@@ -33,6 +37,9 @@ public partial class MainWindowViewModel
|
|||||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||||
[ObservableProperty] private bool _isUpdateShown = false;
|
[ObservableProperty] private bool _isUpdateShown = false;
|
||||||
|
|
||||||
|
[ObservableProperty] private string _schemaVersion = "N/A";
|
||||||
|
[ObservableProperty] private string _schemaVersionLatest = "N/A";
|
||||||
|
|
||||||
public HttpClient HttpClient { get; }
|
public HttpClient HttpClient { get; }
|
||||||
public DialogService DialogService { get; }
|
public DialogService DialogService { get; }
|
||||||
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
|
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
|
||||||
@@ -42,8 +49,25 @@ public partial class MainWindowViewModel
|
|||||||
|
|
||||||
[ObservableProperty] private ObservableCollection<InfoBarViewModel> _infoBarItems = [];
|
[ObservableProperty] private ObservableCollection<InfoBarViewModel> _infoBarItems = [];
|
||||||
|
|
||||||
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService)
|
private readonly ILogger<MainWindowViewModel> _logger;
|
||||||
|
|
||||||
|
private readonly System.Timers.Timer _latestUpdateTimer = new()
|
||||||
{
|
{
|
||||||
|
Interval = TimeSpan.FromMinutes(10).TotalMilliseconds,
|
||||||
|
Enabled = true
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly System.Timers.Timer _schemaVersionTimer = new()
|
||||||
|
{
|
||||||
|
Interval = TimeSpan.FromSeconds(5).TotalMilliseconds,
|
||||||
|
Enabled = true
|
||||||
|
};
|
||||||
|
private bool _isSchemaVersionChecked = false;
|
||||||
|
|
||||||
|
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService, ILogger<MainWindowViewModel> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
MenuItems = new AvaloniaList<NavigationViewItem>(pages
|
MenuItems = new AvaloniaList<NavigationViewItem>(pages
|
||||||
.OrderBy(p => p.Index)
|
.OrderBy(p => p.Index)
|
||||||
.ThenBy(p => p.DisplayName)
|
.ThenBy(p => p.DisplayName)
|
||||||
@@ -61,20 +85,60 @@ public partial class MainWindowViewModel
|
|||||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||||
|
|
||||||
Task.Run(FetchDataAsync);
|
Task.Run(FetchDataAsync);
|
||||||
new Thread(ProcessEvents) { IsBackground = true }.Start();
|
|
||||||
|
_latestUpdateTimer.Elapsed += OnLatestUpdateTimerElapsed;
|
||||||
|
_schemaVersionTimer.Elapsed += OnSchemaVersionTimerElapsed;
|
||||||
|
_latestUpdateTimer.Start();
|
||||||
|
_schemaVersionTimer.Start();
|
||||||
|
OnLatestUpdateTimerElapsed(null, null);
|
||||||
|
OnSchemaVersionTimerElapsed(null, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessEvents(object? obj)
|
private async void OnSchemaVersionTimerElapsed(object? sender, ElapsedEventArgs? e)
|
||||||
{
|
{
|
||||||
while (!IsUpdateShown)
|
if (OpenApiDocumentWrapper == null) return;
|
||||||
{
|
if (!ProcessFinder.IsPortOpen()) return;
|
||||||
Task.Run(CheckLatestVersionAsync);
|
|
||||||
|
|
||||||
Thread.Sleep(TimeSpan.FromMinutes(10)); // Avoid tripping unauthenticated rate limits
|
try
|
||||||
|
{
|
||||||
|
var client = Connector.GetLcuHttpClientInstance();
|
||||||
|
|
||||||
|
var currentSemVer = OpenApiDocumentWrapper.Info.Version.Split('.');
|
||||||
|
var systemBuild = await client.GetFromJsonAsync<SystemBuild>("/system/v1/builds") ?? throw new NullReferenceException();
|
||||||
|
var latestSemVer = systemBuild.Version.Split('.');
|
||||||
|
|
||||||
|
if (!_isSchemaVersionChecked)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("LCU Schema (current): {Version}", OpenApiDocumentWrapper.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Schema version check failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckLatestVersionAsync()
|
private async void OnLatestUpdateTimerElapsed(object? sender, ElapsedEventArgs? e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -83,7 +147,11 @@ public partial class MainWindowViewModel
|
|||||||
|
|
||||||
var response = await HttpClient.SendAsync(request);
|
var response = await HttpClient.SendAsync(request);
|
||||||
var release = await response.Content.ReadFromJsonAsync<GithubRelease>();
|
var release = await response.Content.ReadFromJsonAsync<GithubRelease>();
|
||||||
if (release == null) return;
|
if (release == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Release response is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var currentVersion = int.Parse(Version.Replace(".", ""));
|
var currentVersion = int.Parse(Version.Replace(".", ""));
|
||||||
|
|
||||||
@@ -91,28 +159,40 @@ public partial class MainWindowViewModel
|
|||||||
{
|
{
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(async () =>
|
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(10), new Avalonia.Controls.Button()
|
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,
|
Command = OpenUrlCommand,
|
||||||
CommandParameter = "https://github.com/BlossomiShymae/Needlework.Net/releases",
|
CommandParameter = "https://github.com/BlossomiShymae/Needlework.Net/releases",
|
||||||
Content = "Download"
|
Content = "Download"
|
||||||
}));
|
}));
|
||||||
IsUpdateShown = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_latestUpdateTimer.Elapsed -= OnLatestUpdateTimerElapsed;
|
||||||
|
_latestUpdateTimer.Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception) { }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to check for latest version");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task FetchDataAsync()
|
private async Task FetchDataAsync()
|
||||||
{
|
{
|
||||||
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
try
|
||||||
HostDocument = document;
|
{
|
||||||
var handler = new OpenApiDocumentWrapper(document);
|
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
||||||
OpenApiDocumentWrapper = handler;
|
HostDocument = document;
|
||||||
|
var handler = new OpenApiDocumentWrapper(document);
|
||||||
|
OpenApiDocumentWrapper = handler;
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
||||||
IsBusy = false;
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to fetch OpenAPI data");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(DataRequestMessage message)
|
public void Receive(DataRequestMessage message)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.ViewModels.Shared;
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -14,10 +15,11 @@ public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
|
|||||||
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
|
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
[ObservableProperty] private LcuRequestViewModel _lcuRequest = new();
|
[ObservableProperty] private LcuRequestViewModel _lcuRequest;
|
||||||
|
|
||||||
public ConsoleViewModel() : base("Console", "terminal", -200)
|
public ConsoleViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) : base("Console", "terminal", -200)
|
||||||
{
|
{
|
||||||
|
_lcuRequest = new(lcuRequestViewModelLogger);
|
||||||
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -21,12 +23,12 @@ public partial class EndpointViewModel : ObservableObject
|
|||||||
|
|
||||||
public event EventHandler<string>? PathOperationSelected;
|
public event EventHandler<string>? PathOperationSelected;
|
||||||
|
|
||||||
public EndpointViewModel(string endpoint)
|
public EndpointViewModel(string endpoint, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
|
||||||
{
|
{
|
||||||
Endpoint = endpoint;
|
Endpoint = endpoint;
|
||||||
|
|
||||||
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
|
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
|
||||||
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x)));
|
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x, lcuRequestViewModelLogger)));
|
||||||
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
@@ -15,9 +17,9 @@ public partial class EndpointsNavigationViewModel : ObservableObject
|
|||||||
|
|
||||||
private readonly Action<string?, Guid> _onEndpointNavigation;
|
private readonly Action<string?, Guid> _onEndpointNavigation;
|
||||||
|
|
||||||
public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation)
|
public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
|
||||||
{
|
{
|
||||||
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked);
|
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked, lcuRequestViewModelLogger);
|
||||||
_onEndpointNavigation = onEndpointNavigation;
|
_onEndpointNavigation = onEndpointNavigation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
@@ -16,8 +18,11 @@ public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessa
|
|||||||
|
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
|
||||||
public EndpointsTabViewModel() : base("Endpoints", "list-alt", -500)
|
private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger;
|
||||||
|
|
||||||
|
public EndpointsTabViewModel(ILogger<LcuRequestViewModel> lcuRequestViewModelLogger) : base("Endpoints", "list-alt", -500)
|
||||||
{
|
{
|
||||||
|
_lcuRequestViewModelLogger = lcuRequestViewModelLogger;
|
||||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +40,7 @@ public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessa
|
|||||||
{
|
{
|
||||||
Endpoints.Add(new()
|
Endpoints.Add(new()
|
||||||
{
|
{
|
||||||
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation),
|
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation, _lcuRequestViewModelLogger),
|
||||||
Selected = true
|
Selected = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -16,11 +18,14 @@ public partial class EndpointsViewModel : ObservableObject
|
|||||||
|
|
||||||
public Action<ObservableObject> OnClicked { get; }
|
public Action<ObservableObject> OnClicked { get; }
|
||||||
|
|
||||||
public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked)
|
private readonly ILogger<LcuRequestViewModel> _lcuRequestViewModelLogger;
|
||||||
|
|
||||||
|
public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
|
||||||
{
|
{
|
||||||
Plugins = plugins;
|
Plugins = new AvaloniaList<string>(plugins);
|
||||||
Query = plugins;
|
Query = new AvaloniaList<string>(plugins);
|
||||||
OnClicked = onClicked;
|
OnClicked = onClicked;
|
||||||
|
_lcuRequestViewModelLogger = lcuRequestViewModelLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSearchChanged(string value)
|
partial void OnSearchChanged(string value)
|
||||||
@@ -37,6 +42,6 @@ public partial class EndpointsViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value)) return;
|
if (string.IsNullOrEmpty(value)) return;
|
||||||
|
|
||||||
OnClicked.Invoke(new EndpointViewModel(value));
|
OnClicked.Invoke(new EndpointViewModel(value, _lcuRequestViewModelLogger));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using Avalonia.Controls;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Models;
|
using Needlework.Net.Models;
|
||||||
using Needlework.Net.ViewModels.Shared;
|
using Needlework.Net.ViewModels.Shared;
|
||||||
using System;
|
using System;
|
||||||
@@ -13,17 +15,20 @@ public partial class PathOperationViewModel : ObservableObject
|
|||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
public OperationViewModel Operation { get; }
|
public OperationViewModel Operation { get; }
|
||||||
|
|
||||||
|
public string Url { get; }
|
||||||
|
|
||||||
[ObservableProperty] private bool _isBusy;
|
[ObservableProperty] private bool _isBusy;
|
||||||
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest;
|
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest;
|
||||||
|
|
||||||
public PathOperationViewModel(PathOperation pathOperation)
|
public PathOperationViewModel(PathOperation pathOperation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
|
||||||
{
|
{
|
||||||
Path = pathOperation.Path;
|
Path = pathOperation.Path;
|
||||||
Operation = new OperationViewModel(pathOperation.Operation);
|
Operation = new OperationViewModel(pathOperation.Operation);
|
||||||
LcuRequest = new(() => new LcuRequestViewModel()
|
LcuRequest = new(() => new LcuRequestViewModel(lcuRequestViewModelLogger)
|
||||||
{
|
{
|
||||||
Method = pathOperation.Method.ToUpper()
|
Method = pathOperation.Method.ToUpper()
|
||||||
});
|
});
|
||||||
|
Url = $"https://swagger.dysolix.dev/lcu/#/{pathOperation.Tag}/{pathOperation.Operation.OperationId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -49,4 +54,10 @@ public partial class PathOperationViewModel : ObservableObject
|
|||||||
LcuRequest.Value.RequestPath = sb.ToString();
|
LcuRequest.Value.RequestPath = sb.ToString();
|
||||||
await LcuRequest.Value.ExecuteAsync();
|
await LcuRequest.Value.ExecuteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void CopyUrl()
|
||||||
|
{
|
||||||
|
App.MainWindow?.Clipboard?.SetTextAsync(Url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using Avalonia.Platform;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Needlework.Net.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages;
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
public partial class HomeViewModel : PageBase
|
public partial class HomeViewModel : PageBase
|
||||||
{
|
{
|
||||||
|
public List<Library> Libraries { get; } = JsonSerializer.Deserialize<List<Library>>(AssetLoader.Open(new Uri($"avares://NeedleworkDotNet/Assets/libraries.json")))!;
|
||||||
|
|
||||||
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class EventViewModel : ObservableObject
|
|||||||
public EventViewModel(EventData eventData)
|
public EventViewModel(EventData eventData)
|
||||||
{
|
{
|
||||||
Time = $"{DateTime.Now:HH:mm:ss.fff}";
|
Time = $"{DateTime.Now:HH:mm:ss.fff}";
|
||||||
Type = eventData?.EventType.ToUpper() ?? string.Empty;
|
Type = eventData?.EventType?.ToUpper() ?? string.Empty;
|
||||||
Uri = eventData?.Uri ?? string.Empty;
|
Uri = eventData?.Uri ?? string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
using BlossomiShymae.GrrrLCU;
|
using Avalonia.Collections;
|
||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Websocket.Client;
|
using Websocket.Client;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages.Websocket;
|
namespace Needlework.Net.ViewModels.Pages.Websocket;
|
||||||
@@ -25,37 +29,89 @@ public partial class WebsocketViewModel : PageBase
|
|||||||
[ObservableProperty] private bool _isTail = false;
|
[ObservableProperty] private bool _isTail = false;
|
||||||
[ObservableProperty] private EventViewModel? _selectedEventLog = null;
|
[ObservableProperty] private EventViewModel? _selectedEventLog = null;
|
||||||
|
|
||||||
|
[ObservableProperty] private IAvaloniaList<string> _eventTypes = new AvaloniaList<string>();
|
||||||
|
[ObservableProperty] private string _eventType = "OnJsonApiEvent";
|
||||||
|
|
||||||
private Dictionary<string, EventMessage> _events = [];
|
private Dictionary<string, EventMessage> _events = [];
|
||||||
|
|
||||||
public WebsocketClient? Client { get; set; }
|
public WebsocketClient? Client { get; set; }
|
||||||
|
|
||||||
|
public List<IDisposable> ClientDisposables = [];
|
||||||
|
|
||||||
|
private readonly object _tokenLock = new();
|
||||||
|
public CancellationTokenSource TokenSource { get; set; } = new();
|
||||||
|
|
||||||
|
public HttpClient HttpClient { get; }
|
||||||
|
|
||||||
public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
||||||
|
|
||||||
public WebsocketViewModel() : base("Event Viewer", "plug", -100)
|
private readonly ILogger<WebsocketViewModel> _logger;
|
||||||
|
|
||||||
|
public WebsocketViewModel(HttpClient httpClient, ILogger<WebsocketViewModel> logger) : base("Event Viewer", "plug", -100)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
|
HttpClient = httpClient;
|
||||||
EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
||||||
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
Task.Run(async () =>
|
||||||
thread.Start();
|
{
|
||||||
|
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 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))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeWebsocket()
|
private void InitializeWebsocket()
|
||||||
{
|
{
|
||||||
while (true)
|
lock (_tokenLock)
|
||||||
{
|
{
|
||||||
try
|
if (Client != null)
|
||||||
{
|
{
|
||||||
var client = Connector.CreateLcuWebsocketClient();
|
_logger.LogDebug("Disposing old connection");
|
||||||
client.EventReceived.Subscribe(OnMessage);
|
foreach (var disposable in ClientDisposables)
|
||||||
client.DisconnectionHappened.Subscribe(OnDisconnection);
|
disposable.Dispose();
|
||||||
client.ReconnectionHappened.Subscribe(OnReconnection);
|
ClientDisposables.Clear();
|
||||||
|
Client.Dispose();
|
||||||
client.Start();
|
|
||||||
client.Send(new EventMessage(EventRequestType.Subscribe, EventKinds.OnJsonApiEvent));
|
|
||||||
Client = client;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
catch (Exception) { }
|
TokenSource.Cancel();
|
||||||
Thread.Sleep(TimeSpan.FromSeconds(5));
|
var tokenSource = new CancellationTokenSource();
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
while (!tokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = Connector.CreateLcuWebsocketClient();
|
||||||
|
ClientDisposables.Add(client.EventReceived.Subscribe(OnMessage));
|
||||||
|
ClientDisposables.Add(client.DisconnectionHappened.Subscribe(OnDisconnection));
|
||||||
|
ClientDisposables.Add(client.ReconnectionHappened.Subscribe(OnReconnection));
|
||||||
|
|
||||||
|
client.Start();
|
||||||
|
client.Send(new EventMessage(EventRequestType.Subscribe, new EventKind() { Prefix = EventType }));
|
||||||
|
Client = client;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception) { }
|
||||||
|
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{ IsBackground = true };
|
||||||
|
thread.Start();
|
||||||
|
_logger.LogDebug("Initialized new connection: {EventType}", EventType);
|
||||||
|
TokenSource = tokenSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,15 +135,18 @@ public partial class WebsocketViewModel : PageBase
|
|||||||
|
|
||||||
private void OnReconnection(ReconnectionInfo info)
|
private void OnReconnection(ReconnectionInfo info)
|
||||||
{
|
{
|
||||||
Trace.WriteLine($"-- Reconnection --\nType{info.Type}");
|
_logger.LogTrace("Reconnected: {Type}", info.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisconnection(DisconnectionInfo info)
|
private void OnDisconnection(DisconnectionInfo info)
|
||||||
{
|
{
|
||||||
Trace.WriteLine($"-- Disconnection --\nType:{info.Type}\nSubProocol:{info.SubProtocol}\nCloseStatus:{info.CloseStatus}\nCloseStatusDescription:{info.CloseStatusDescription}\nExceptionMessage:{info?.Exception?.Message}\n:InnerException:{info?.Exception?.InnerException}");
|
_logger.LogTrace("Disconnected: {Type}", info.Type);
|
||||||
Client?.Dispose();
|
InitializeWebsocket();
|
||||||
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
}
|
||||||
thread.Start();
|
|
||||||
|
partial void OnEventTypeChanged(string value)
|
||||||
|
{
|
||||||
|
InitializeWebsocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMessage(EventMessage message)
|
private void OnMessage(EventMessage message)
|
||||||
@@ -122,4 +181,7 @@ public partial class WebsocketViewModel : PageBase
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("\"(.*?)\":")]
|
||||||
|
public static partial Regex EventTypesRegex();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using BlossomiShymae.GrrrLCU;
|
using BlossomiShymae.GrrrLCU;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.ViewModels.MainWindow;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
using System;
|
using System;
|
||||||
@@ -31,6 +32,13 @@ public partial class LcuRequestViewModel : ObservableObject
|
|||||||
public event EventHandler<LcuRequestViewModel>? RequestText;
|
public event EventHandler<LcuRequestViewModel>? RequestText;
|
||||||
public event EventHandler<string>? UpdateText;
|
public event EventHandler<string>? UpdateText;
|
||||||
|
|
||||||
|
private readonly ILogger<LcuRequestViewModel> _logger;
|
||||||
|
|
||||||
|
public LcuRequestViewModel(ILogger<LcuRequestViewModel> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
partial void OnMethodChanged(string? oldValue, string? newValue)
|
partial void OnMethodChanged(string? oldValue, string? newValue)
|
||||||
{
|
{
|
||||||
if (newValue == null) return;
|
if (newValue == null) return;
|
||||||
@@ -59,6 +67,8 @@ public partial class LcuRequestViewModel : ObservableObject
|
|||||||
_ => throw new Exception("Method is not selected or missing."),
|
_ => throw new Exception("Method is not selected or missing."),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath));
|
||||||
|
|
||||||
var processInfo = ProcessFinder.GetProcessInfo();
|
var processInfo = ProcessFinder.GetProcessInfo();
|
||||||
RequestText?.Invoke(this, this);
|
RequestText?.Invoke(this, this);
|
||||||
var content = new StringContent(RequestBody ?? string.Empty, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
var content = new StringContent(RequestBody ?? string.Empty, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||||
@@ -88,6 +98,7 @@ public partial class LcuRequestViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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))));
|
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);
|
UpdateText?.Invoke(this, string.Empty);
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
<TransitioningContentControl Content="{Binding CurrentPage}"/>
|
<TransitioningContentControl Content="{Binding CurrentPage}"/>
|
||||||
<Button Content="{Binding Version}"
|
<Button Content="{Binding Version}"
|
||||||
|
Background="RoyalBlue"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
Margin="16"/>
|
Margin="16"/>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Avalonia.Controls;
|
||||||
using FluentAvalonia.UI.Windowing;
|
using FluentAvalonia.UI.Windowing;
|
||||||
|
|
||||||
namespace Needlework.Net.Views.MainWindow;
|
namespace Needlework.Net.Views.MainWindow;
|
||||||
@@ -9,5 +12,19 @@ public partial class MainWindowView : AppWindow
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||||
|
TransparencyLevelHint = [WindowTransparencyLevel.Mica, WindowTransparencyLevel.None];
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
if (IsWindows11OrNewer())
|
||||||
|
{
|
||||||
|
Background = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsWindows11OrNewer()
|
||||||
|
{
|
||||||
|
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 22000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,23 +55,28 @@
|
|||||||
<Grid
|
<Grid
|
||||||
RowDefinitions="*"
|
RowDefinitions="*"
|
||||||
ColumnDefinitions="auto,*">
|
ColumnDefinitions="auto,*">
|
||||||
<TextBlock
|
<Grid.ContextFlyout>
|
||||||
VerticalAlignment="Center"
|
<MenuFlyout>
|
||||||
TextAlignment="Center"
|
<MenuItem Header="Copy Swagger URL" Command="{Binding CopyUrlCommand}"/>
|
||||||
Margin="0 0 8 0"
|
</MenuFlyout>
|
||||||
Text="{Binding LcuRequest.Value.Method}"
|
</Grid.ContextFlyout>
|
||||||
Background="{Binding LcuRequest.Value.Color}"
|
<TextBlock
|
||||||
FontSize="8"
|
VerticalAlignment="Center"
|
||||||
Width="50"
|
TextAlignment="Center"
|
||||||
Padding="10 2 10 2"
|
Margin="0 0 8 0"
|
||||||
Grid.Row="0"
|
Text="{Binding LcuRequest.Value.Method}"
|
||||||
Grid.Column="0"/>
|
Background="{Binding LcuRequest.Value.Color}"
|
||||||
<TextBlock
|
FontSize="8"
|
||||||
VerticalAlignment="Center"
|
Width="50"
|
||||||
Text="{Binding Path}"
|
Padding="10 2 10 2"
|
||||||
FontSize="11"
|
Grid.Row="0"
|
||||||
Grid.Row="0"
|
Grid.Column="0"/>
|
||||||
Grid.Column="1"/>
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Path}"
|
||||||
|
FontSize="11"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
|
|||||||
@@ -15,6 +15,27 @@
|
|||||||
<ui:TabView TabItems="{Binding Endpoints}"
|
<ui:TabView TabItems="{Binding Endpoints}"
|
||||||
AddTabButtonCommand="{Binding AddEndpointCommand}"
|
AddTabButtonCommand="{Binding AddEndpointCommand}"
|
||||||
TabCloseRequested="TabView_TabCloseRequested">
|
TabCloseRequested="TabView_TabCloseRequested">
|
||||||
|
<!--Need to override Tab header for Mica theme...-->
|
||||||
|
<ui:TabView.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<SolidColorBrush x:Key="TabViewItemHeaderBackgroundSelected" Color="{DynamicResource ControlFillColorTransparent}"/>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ui:TabView.Resources>
|
||||||
|
<!--We need to hack this style for Mica theme since there is no way to explicity set style priority in Avalonia...-->
|
||||||
|
<ui:TabView.Styles>
|
||||||
|
<Style Selector="Grid > ContentPresenter#TabContentPresenter">
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation IterationCount="1" Duration="0:0:1" FillMode="Both">
|
||||||
|
<KeyFrame Cue="0%">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ControlFillColorTransparent}"/>
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="100%">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ControlFillColorTransparent}"/>
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
</ui:TabView.Styles>
|
||||||
<ui:TabView.TabItemTemplate>
|
<ui:TabView.TabItemTemplate>
|
||||||
<DataTemplate DataType="vm:EndpointItem">
|
<DataTemplate DataType="vm:EndpointItem">
|
||||||
<ui:TabViewItem Header="{Binding Header}"
|
<ui:TabViewItem Header="{Binding Header}"
|
||||||
|
|||||||
@@ -5,59 +5,121 @@
|
|||||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
Name="HomeControl"
|
||||||
x:Class="Needlework.Net.Views.Pages.HomeView"
|
x:Class="Needlework.Net.Views.Pages.HomeView"
|
||||||
x:DataType="vm:HomeViewModel">
|
x:DataType="vm:HomeViewModel">
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Command" Value="{Binding OpenUrlCommand}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="DataGrid">
|
||||||
|
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource ControlElevationBorderBrush}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="DataGridColumnHeader TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="DataGridRow DataGridCell">
|
||||||
|
<Setter Property="FontSize" Value="12"></Setter>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="DataGridRow">
|
||||||
|
<Setter Property="Margin" Value="0 0 0 4"></Setter>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
<!-- TOP LEVEL -->
|
<!-- TOP LEVEL -->
|
||||||
<ScrollViewer>
|
<Grid ColumnDefinitions="*,400"
|
||||||
<WrapPanel Margin="8"
|
RowDefinitions="*">
|
||||||
Orientation="Horizontal">
|
<!-- MAIN AREA -->
|
||||||
<!-- WELCOME -->
|
<ScrollViewer Grid.Column="0"
|
||||||
<StackPanel>
|
Grid.Row="0">
|
||||||
<Border Margin="12">
|
<WrapPanel Margin="8"
|
||||||
<StackPanel>
|
Orientation="Horizontal">
|
||||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">
|
<!-- WELCOME -->
|
||||||
Welcome to Needlework.Net
|
<StackPanel>
|
||||||
</TextBlock>
|
<Border Margin="12">
|
||||||
<TextBlock>Get started with LCU development by clicking on the endpoints tab in the left panel.</TextBlock>
|
<StackPanel>
|
||||||
</StackPanel>
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">
|
||||||
</Border>
|
Welcome to Needlework.Net
|
||||||
<controls:Card Margin="12">
|
</TextBlock>
|
||||||
<TextBlock TextWrapping="Wrap">THE PROGRAM IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGMENT, OR OF FITNESS FOR A PARTICULAR PURPOSE. LICENSOR DOES NOT WARRANT THAT THE FUNCTIONS CONTAINED IN THE PROGRAM WILL MEET YOUR REQUIREMENTS OR THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. LICENSOR MAKES NO WARRANTIES RESPECTING ANY HARM THAT MAY BE CAUSED BY MALICIOUS USE OF THIS SOFTWARE. LICENSOR FURTHER EXPRESSLY DISCLAIMS ANY WARRANTY OR REPRESENTATION TO AUTHORIZED USERS OR TO ANY THIRD PARTY.</TextBlock>
|
<TextBlock>Get started with LCU development by clicking on the endpoints tab in the left panel.</TextBlock>
|
||||||
</controls:Card>
|
|
||||||
</StackPanel>
|
|
||||||
<!-- FOOTER -->
|
|
||||||
<StackPanel>
|
|
||||||
<controls:Card Margin="12" Width="300">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock
|
|
||||||
Theme="{StaticResource SubtitleTextBlockStyle}"
|
|
||||||
Margin="0 0 0 8">Resources</TextBlock>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<StackPanel.Styles>
|
|
||||||
<Style Selector="Button">
|
|
||||||
<Setter Property="Command" Value="{Binding OpenUrlCommand}"/>
|
|
||||||
</Style>
|
|
||||||
</StackPanel.Styles>
|
|
||||||
<Button CommandParameter="https://hextechdocs.dev/tag/lcu/" Margin="0 0 8 0">
|
|
||||||
Hextech Docs
|
|
||||||
</Button>
|
|
||||||
<Button CommandParameter="https://hextechdocs.dev/getting-started-with-the-lcu-api/">
|
|
||||||
Getting Started
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</Border>
|
||||||
</controls:Card>
|
<controls:Card Margin="12">
|
||||||
|
<TextBlock TextWrapping="Wrap">THE PROGRAM IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGMENT, OR OF FITNESS FOR A PARTICULAR PURPOSE. LICENSOR DOES NOT WARRANT THAT THE FUNCTIONS CONTAINED IN THE PROGRAM WILL MEET YOUR REQUIREMENTS OR THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. LICENSOR MAKES NO WARRANTIES RESPECTING ANY HARM THAT MAY BE CAUSED BY MALICIOUS USE OF THIS SOFTWARE. LICENSOR FURTHER EXPRESSLY DISCLAIMS ANY WARRANTY OR REPRESENTATION TO AUTHORIZED USERS OR TO ANY THIRD PARTY.</TextBlock>
|
||||||
|
</controls:Card>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- FOOTER -->
|
||||||
|
<StackPanel>
|
||||||
|
<controls:Card Margin="12" Width="300">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||||
|
Margin="0 0 0 8">Resources</TextBlock>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
|
<Button CommandParameter="https://hextechdocs.dev/tag/lcu/" Margin="4">
|
||||||
|
Hextech Docs
|
||||||
|
</Button>
|
||||||
|
<Button CommandParameter="https://hextechdocs.dev/getting-started-with-the-lcu-api/" Margin="4">
|
||||||
|
Getting Started
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
|
<Button CommandParameter="https://discord.com/channels/187652476080488449/516802588805431296" Margin="4">
|
||||||
|
#lcu-api
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</controls:Card>
|
||||||
|
<controls:Card Margin="12" Width="300">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock>© 2025 - Blossomi Shymae</TextBlock>
|
||||||
|
<TextBlock>MIT License</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</controls:Card>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- LEGAL -->
|
||||||
<controls:Card Margin="12" Width="300">
|
<controls:Card Margin="12" Width="300">
|
||||||
<StackPanel>
|
<TextBlock TextWrapping="Wrap">Needlework.Net isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties. Riot Games, and all associated properties are trademarks or registered trademarks of Riot Games, Inc.</TextBlock>
|
||||||
<TextBlock>© 2024 - Blossomi Shymae</TextBlock>
|
|
||||||
<TextBlock>MIT License</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</controls:Card>
|
</controls:Card>
|
||||||
</StackPanel>
|
</WrapPanel>
|
||||||
<!-- LEGAL -->
|
</ScrollViewer>
|
||||||
<controls:Card Margin="12" Width="300">
|
<!-- LIBRARIES -->
|
||||||
<TextBlock TextWrapping="Wrap">Needlework.Net isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties. Riot Games, and all associated properties are trademarks or registered trademarks of Riot Games, Inc.</TextBlock>
|
<Grid Margin="20"
|
||||||
</controls:Card>
|
Grid.Column="1"
|
||||||
</WrapPanel>
|
Grid.Row="0"
|
||||||
</ScrollViewer>
|
ColumnDefinitions="*"
|
||||||
|
RowDefinitions="auto,*">
|
||||||
|
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.Row="0">Libraries</TextBlock>
|
||||||
|
<ScrollViewer Grid.Column="0"
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<ItemsRepeater ItemsSource="{Binding Libraries}">
|
||||||
|
<ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Margin="0 12 0 0">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="{Binding Language}"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
<Bold> - </Bold>
|
||||||
|
<Run Text="{Binding Repo}"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Text="{Binding Description}"
|
||||||
|
IsVisible="{Binding Description, Converter={StaticResource NullBoolConverter}}"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="WrapWithOverflow"
|
||||||
|
Width="350"/>
|
||||||
|
<Button Command="{Binding #HomeControl.((vm:HomeViewModel)DataContext).OpenUrlCommand}"
|
||||||
|
CommandParameter="{Binding Link}"
|
||||||
|
Margin="0 4 0 0">
|
||||||
|
<TextBlock Text="{Binding Link}"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsRepeater.ItemTemplate>
|
||||||
|
</ItemsRepeater>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -10,12 +10,22 @@
|
|||||||
<Grid RowDefinitions="*,auto,*" Margin="16">
|
<Grid RowDefinitions="*,auto,*" Margin="16">
|
||||||
<Border Grid.Row="0"
|
<Border Grid.Row="0"
|
||||||
Padding="0 0 0 8">
|
Padding="0 0 0 8">
|
||||||
<Grid RowDefinitions="auto,*" ColumnDefinitions="*">
|
<Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||||
|
<Grid Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
RowDefinitions="*">
|
||||||
|
<ComboBox ItemsSource="{Binding EventTypes}"
|
||||||
|
SelectedItem="{Binding EventType}"
|
||||||
|
Grid.Row="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Left"/>
|
||||||
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
Grid.Row="0"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
RowDefinitions="*"
|
RowDefinitions="*"
|
||||||
ColumnDefinitions="auto,*,auto,auto">
|
ColumnDefinitions="auto,*,auto,auto"
|
||||||
|
Margin="0 8 0 0">
|
||||||
<Button Margin="0 0 8 0"
|
<Button Margin="0 0 8 0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -37,7 +47,7 @@
|
|||||||
Content="Tail"
|
Content="Tail"
|
||||||
IsChecked="{Binding IsTail}"/>
|
IsChecked="{Binding IsTail}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ListBox Grid.Row="1"
|
<ListBox Grid.Row="2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Name="EventViewer"
|
Name="EventViewer"
|
||||||
Margin="0 8 0 0"
|
Margin="0 8 0 0"
|
||||||
|
|||||||
BIN
app-preview.png
BIN
app-preview.png
Binary file not shown.
|
Before Width: | Height: | Size: 370 KiB After Width: | Height: | Size: 397 KiB |
Reference in New Issue
Block a user