11 Commits

Author SHA1 Message Date
BlossomiShymae
b63713f054 Bump version 2024-12-18 01:07:25 -06:00
BlossomiShymae
6a776dfd5f Add #lcu-api channel link 2024-12-17 23:49:13 -06:00
BlossomiShymae
9270c6d1f1 Add logging 2024-12-17 23:36:28 -06:00
BlossomiShymae
f65c6f1b09 Update and add packages 2024-12-17 21:09:40 -06:00
BlossomiShymae
bd6589c310 Bump version 2024-12-15 23:22:52 -06:00
BlossomiShymae
cf947f3af4 Update event types URL 2024-12-15 23:22:30 -06:00
BlossomiShymae
2e4637f533 Bump version 2024-12-06 22:18:26 -06:00
BlossomiShymae
7aaa79956c Add event type selection for event viewer 2024-12-06 22:17:48 -06:00
BlossomiShymae
e9d4615ecf Add libraries list 2024-12-06 18:59:43 -06:00
BlossomiShymae
fb63adc1b7 Bump version 2024-12-06 16:25:33 -06:00
BlossomiShymae
b41be19cd9 Fix bug where endpoints search breaks, resolves #6 2024-12-06 16:19:38 -06:00
18 changed files with 318 additions and 106 deletions

View 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"}]

View 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; }
}

View File

@@ -11,7 +11,7 @@
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
<ApplicationIcon>app.ico</ApplicationIcon>
<AssemblyName>NeedleworkDotNet</AssemblyName>
<AssemblyVersion>0.8.0.0</AssemblyVersion>
<AssemblyVersion>0.10.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
</PropertyGroup>
@@ -31,13 +31,17 @@
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.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.Readers" Version="1.6.22" />
<PackageReference Include="Projektanker.Icons.Avalonia" 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>

View File

@@ -6,8 +6,10 @@ using Needlework.Net.ViewModels.MainWindow;
using Needlework.Net.ViewModels.Pages;
using Projektanker.Icons.Avalonia;
using Projektanker.Icons.Avalonia.FontAwesome;
using Serilog;
using System;
using System.IO;
using System.Reflection;
namespace Needlework.Net;
@@ -44,9 +46,16 @@ class Program
builder.AddSingleton<MainWindowViewModel>();
builder.AddSingleton<DialogService>();
builder.AddSingletonsFromAssemblies<PageBase>();
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();
return services;
}

View File

@@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Needlework.Net.Messages;
using Needlework.Net.Models;
@@ -42,8 +43,12 @@ public partial class MainWindowViewModel
[ObservableProperty] private ObservableCollection<InfoBarViewModel> _infoBarItems = [];
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService)
private readonly ILogger<MainWindowViewModel> _logger;
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService, ILogger<MainWindowViewModel> logger)
{
_logger = logger;
MenuItems = new AvaloniaList<NavigationViewItem>(pages
.OrderBy(p => p.Index)
.ThenBy(p => p.DisplayName)
@@ -83,7 +88,11 @@ public partial class MainWindowViewModel
var response = await HttpClient.SendAsync(request);
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(".", ""));
@@ -101,18 +110,28 @@ public partial class MainWindowViewModel
});
}
}
catch (Exception) { }
catch (Exception ex)
{
_logger.LogError(ex, "Failed to check for latest version");
}
}
private async Task FetchDataAsync()
{
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
HostDocument = document;
var handler = new OpenApiDocumentWrapper(document);
OpenApiDocumentWrapper = handler;
try
{
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
HostDocument = document;
var handler = new OpenApiDocumentWrapper(document);
OpenApiDocumentWrapper = handler;
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
IsBusy = false;
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
IsBusy = false;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Failed to fetch OpenAPI data");
}
}
public void Receive(DataRequestMessage message)

View File

@@ -2,6 +2,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.Shared;
using System.Threading.Tasks;
@@ -14,10 +15,11 @@ public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
[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);
}

View File

@@ -1,7 +1,9 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.Shared;
using System;
using System.Linq;
@@ -21,12 +23,12 @@ public partial class EndpointViewModel : ObservableObject
public event EventHandler<string>? PathOperationSelected;
public EndpointViewModel(string endpoint)
public EndpointViewModel(string endpoint, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
{
Endpoint = endpoint;
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);
}

View File

@@ -1,6 +1,8 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using Needlework.Net.ViewModels.Shared;
using System;
namespace Needlework.Net.ViewModels.Pages.Endpoints;
@@ -15,9 +17,9 @@ public partial class EndpointsNavigationViewModel : ObservableObject
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;
}

View File

@@ -4,7 +4,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.Shared;
using System;
namespace Needlework.Net.ViewModels.Pages.Endpoints;
@@ -16,8 +18,11 @@ public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessa
[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);
}
@@ -35,7 +40,7 @@ public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessa
{
Endpoints.Add(new()
{
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation),
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation, _lcuRequestViewModelLogger),
Selected = true
});
}

View File

@@ -1,6 +1,8 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using Needlework.Net.ViewModels.Shared;
using System;
using System.Linq;
@@ -16,11 +18,14 @@ public partial class EndpointsViewModel : ObservableObject
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;
Query = plugins;
Plugins = new AvaloniaList<string>(plugins);
Query = new AvaloniaList<string>(plugins);
OnClicked = onClicked;
_lcuRequestViewModelLogger = lcuRequestViewModelLogger;
}
partial void OnSearchChanged(string value)
@@ -37,6 +42,6 @@ public partial class EndpointsViewModel : ObservableObject
{
if (string.IsNullOrEmpty(value)) return;
OnClicked.Invoke(new EndpointViewModel(value));
OnClicked.Invoke(new EndpointViewModel(value, _lcuRequestViewModelLogger));
}
}

View File

@@ -1,5 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using Needlework.Net.Models;
using Needlework.Net.ViewModels.Shared;
using System;
@@ -16,11 +17,11 @@ public partial class PathOperationViewModel : ObservableObject
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest;
public PathOperationViewModel(PathOperation pathOperation)
public PathOperationViewModel(PathOperation pathOperation, ILogger<LcuRequestViewModel> lcuRequestViewModelLogger)
{
Path = pathOperation.Path;
Operation = new OperationViewModel(pathOperation.Operation);
LcuRequest = new(() => new LcuRequestViewModel()
LcuRequest = new(() => new LcuRequestViewModel(lcuRequestViewModelLogger)
{
Method = pathOperation.Method.ToUpper()
});

View File

@@ -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.Text.Json;
namespace Needlework.Net.ViewModels.Pages;
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) { }
[RelayCommand]

View File

@@ -15,7 +15,7 @@ public class EventViewModel : ObservableObject
public EventViewModel(EventData eventData)
{
Time = $"{DateTime.Now:HH:mm:ss.fff}";
Type = eventData?.EventType.ToUpper() ?? string.Empty;
Type = eventData?.EventType?.ToUpper() ?? string.Empty;
Uri = eventData?.Uri ?? string.Empty;
}
}

View File

@@ -1,15 +1,19 @@
using BlossomiShymae.GrrrLCU;
using Avalonia.Collections;
using BlossomiShymae.GrrrLCU;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Websocket.Client;
namespace Needlework.Net.ViewModels.Pages.Websocket;
@@ -25,37 +29,89 @@ public partial class WebsocketViewModel : PageBase
[ObservableProperty] private bool _isTail = false;
[ObservableProperty] private EventViewModel? _selectedEventLog = null;
[ObservableProperty] private IAvaloniaList<string> _eventTypes = new AvaloniaList<string>();
[ObservableProperty] private string _eventType = "OnJsonApiEvent";
private Dictionary<string, EventMessage> _events = [];
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 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));
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
thread.Start();
Task.Run(async () =>
{
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()
{
while (true)
lock (_tokenLock)
{
try
if (Client != null)
{
var client = Connector.CreateLcuWebsocketClient();
client.EventReceived.Subscribe(OnMessage);
client.DisconnectionHappened.Subscribe(OnDisconnection);
client.ReconnectionHappened.Subscribe(OnReconnection);
client.Start();
client.Send(new EventMessage(EventRequestType.Subscribe, EventKinds.OnJsonApiEvent));
Client = client;
return;
_logger.LogDebug("Disposing old connection");
foreach (var disposable in ClientDisposables)
disposable.Dispose();
ClientDisposables.Clear();
Client.Dispose();
}
catch (Exception) { }
Thread.Sleep(TimeSpan.FromSeconds(5));
TokenSource.Cancel();
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)
{
Trace.WriteLine($"-- Reconnection --\nType{info.Type}");
_logger.LogTrace("Reconnected: {Type}", info.Type);
}
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}");
Client?.Dispose();
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
thread.Start();
_logger.LogTrace("Disconnected: {Type}", info.Type);
InitializeWebsocket();
}
partial void OnEventTypeChanged(string value)
{
InitializeWebsocket();
}
private void OnMessage(EventMessage message)
@@ -122,4 +181,7 @@ public partial class WebsocketViewModel : PageBase
}
});
}
[GeneratedRegex("\"(.*?)\":")]
public static partial Regex EventTypesRegex();
}

View File

@@ -2,6 +2,7 @@
using BlossomiShymae.GrrrLCU;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Needlework.Net.Messages;
using Needlework.Net.ViewModels.MainWindow;
using System;
@@ -31,6 +32,13 @@ public partial class LcuRequestViewModel : ObservableObject
public event EventHandler<LcuRequestViewModel>? RequestText;
public event EventHandler<string>? UpdateText;
private readonly ILogger<LcuRequestViewModel> _logger;
public LcuRequestViewModel(ILogger<LcuRequestViewModel> logger)
{
_logger = logger;
}
partial void OnMethodChanged(string? oldValue, string? newValue)
{
if (newValue == null) return;
@@ -59,6 +67,8 @@ public partial class LcuRequestViewModel : ObservableObject
_ => throw new Exception("Method is not selected or missing."),
};
_logger.LogDebug("Sending request: {Tuple}", (Method, RequestPath));
var processInfo = ProcessFinder.GetProcessInfo();
RequestText?.Invoke(this, this);
var content = new StringContent(RequestBody ?? string.Empty, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
@@ -88,6 +98,7 @@ public partial class LcuRequestViewModel : ObservableObject
}
catch (Exception ex)
{
_logger.LogError(ex, "Request failed: {Tuple}", (Method, RequestPath));
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
UpdateText?.Invoke(this, string.Empty);

View File

@@ -76,6 +76,7 @@
<Grid>
<TransitioningContentControl Content="{Binding CurrentPage}"/>
<Button Content="{Binding Version}"
Background="RoyalBlue"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="16"/>

View File

@@ -5,59 +5,121 @@
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
xmlns:controls="using:Needlework.Net.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Name="HomeControl"
x:Class="Needlework.Net.Views.Pages.HomeView"
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 -->
<ScrollViewer>
<WrapPanel Margin="8"
Orientation="Horizontal">
<!-- WELCOME -->
<StackPanel>
<Border Margin="12">
<StackPanel>
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">
Welcome to Needlework.Net
</TextBlock>
<TextBlock>Get started with LCU development by clicking on the endpoints tab in the left panel.</TextBlock>
</StackPanel>
</Border>
<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">
<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>
<Grid ColumnDefinitions="*,400"
RowDefinitions="*">
<!-- MAIN AREA -->
<ScrollViewer Grid.Column="0"
Grid.Row="0">
<WrapPanel Margin="8"
Orientation="Horizontal">
<!-- WELCOME -->
<StackPanel>
<Border Margin="12">
<StackPanel>
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">
Welcome to Needlework.Net
</TextBlock>
<TextBlock>Get started with LCU development by clicking on the endpoints tab in the left panel.</TextBlock>
</StackPanel>
</StackPanel>
</controls:Card>
</Border>
<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>© 2024 - Blossomi Shymae</TextBlock>
<TextBlock>MIT License</TextBlock>
</StackPanel>
</controls:Card>
</StackPanel>
<!-- LEGAL -->
<controls:Card Margin="12" Width="300">
<StackPanel>
<TextBlock>© 2024 - Blossomi Shymae</TextBlock>
<TextBlock>MIT License</TextBlock>
</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>
</controls:Card>
</StackPanel>
<!-- LEGAL -->
<controls:Card Margin="12" Width="300">
<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>
</controls:Card>
</WrapPanel>
</ScrollViewer>
</WrapPanel>
</ScrollViewer>
<!-- LIBRARIES -->
<Grid Margin="20"
Grid.Column="1"
Grid.Row="0"
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>

View File

@@ -10,12 +10,22 @@
<Grid RowDefinitions="*,auto,*" Margin="16">
<Border Grid.Row="0"
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.Row="0"
Grid.Row="1"
Grid.Column="0"
RowDefinitions="*"
ColumnDefinitions="auto,*,auto,auto">
ColumnDefinitions="auto,*,auto,auto"
Margin="0 8 0 0">
<Button Margin="0 0 8 0"
Grid.Row="0"
Grid.Column="0"
@@ -37,7 +47,7 @@
Content="Tail"
IsChecked="{Binding IsTail}"/>
</Grid>
<ListBox Grid.Row="1"
<ListBox Grid.Row="2"
Grid.Column="0"
Name="EventViewer"
Margin="0 8 0 0"