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,10 +110,15 @@ public partial class MainWindowViewModel
});
}
}
catch (Exception) { }
catch (Exception ex)
{
_logger.LogError(ex, "Failed to check for latest version");
}
}
private async Task FetchDataAsync()
{
try
{
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
HostDocument = document;
@@ -114,6 +128,11 @@ public partial class MainWindowViewModel
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,38 +29,90 @@ 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)
{
if (Client != null)
{
_logger.LogDebug("Disposing old connection");
foreach (var disposable in ClientDisposables)
disposable.Dispose();
ClientDisposables.Clear();
Client.Dispose();
}
TokenSource.Cancel();
var tokenSource = new CancellationTokenSource();
var thread = new Thread(() =>
{
while (!tokenSource.IsCancellationRequested)
{
try
{
var client = Connector.CreateLcuWebsocketClient();
client.EventReceived.Subscribe(OnMessage);
client.DisconnectionHappened.Subscribe(OnDisconnection);
client.ReconnectionHappened.Subscribe(OnReconnection);
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, EventKinds.OnJsonApiEvent));
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;
}
}
partial void OnSelectedEventLogChanged(EventViewModel? value)
@@ -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,10 +5,32 @@
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>
<Grid ColumnDefinitions="*,400"
RowDefinitions="*">
<!-- MAIN AREA -->
<ScrollViewer Grid.Column="0"
Grid.Row="0">
<WrapPanel Margin="8"
Orientation="Horizontal">
<!-- WELCOME -->
@@ -32,19 +54,19 @@
<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">
<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/">
<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">
@@ -60,4 +82,44 @@
</controls:Card>
</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
<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="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"