mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 18:20:47 +01:00
Compare commits
40 Commits
0.8.1.0
...
reactiveui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b831b6c1f | ||
|
|
9515377df9 | ||
|
|
8821119c18 | ||
|
|
d0a48e3490 | ||
|
|
e95aa987a1 | ||
|
|
7997cf222c | ||
|
|
a321d84757 | ||
|
|
4bef9a20dd | ||
|
|
fb5fbe1fea | ||
|
|
adc8b0c0f1 | ||
|
|
be7d575b48 | ||
|
|
f9dd654b6a | ||
|
|
57d3eb4172 | ||
|
|
ce2336ab4d | ||
|
|
9a76e1af4a | ||
|
|
6f0126863b | ||
|
|
826134888e | ||
|
|
ef16642c04 | ||
|
|
a5f49c48b8 | ||
|
|
1364cdc38c | ||
|
|
c51f20a324 | ||
|
|
6d1acee8df | ||
|
|
375d5a2ff8 | ||
|
|
2aa77f3e02 | ||
|
|
576863bd72 | ||
|
|
68e5abd1d1 | ||
|
|
b18f425257 | ||
|
|
5ebed22ae3 | ||
|
|
dc44cf72df | ||
|
|
01cb8886c6 | ||
|
|
38e4a64bb8 | ||
|
|
b63713f054 | ||
|
|
6a776dfd5f | ||
|
|
9270c6d1f1 | ||
|
|
f65c6f1b09 | ||
|
|
bd6589c310 | ||
|
|
cf947f3af4 | ||
|
|
2e4637f533 | ||
|
|
7aaa79956c | ||
|
|
e9d4615ecf |
@@ -7,14 +7,11 @@
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
RequestedThemeVariant="Dark">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<sty:FluentAvaloniaTheme PreferSystemTheme="False" PreferUserAccentColor="False" />
|
||||
<sty:FluentAvaloniaTheme />
|
||||
<materialIcons:MaterialIconStyles />
|
||||
<StyleInclude Source="Controls/Card.axaml"/>
|
||||
<StyleInclude Source="Controls/UserCard.axaml"/>
|
||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
</Application.Styles>
|
||||
|
||||
@@ -2,18 +2,31 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Flurl.Http.Configuration;
|
||||
using Needlework.Net.Converters;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.MainWindow;
|
||||
using Needlework.Net.ViewModels.Pages;
|
||||
using Needlework.Net.ViewModels.Pages.About;
|
||||
using Needlework.Net.ViewModels.Pages.Console;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using Needlework.Net.ViewModels.Pages.Home;
|
||||
using Needlework.Net.ViewModels.Pages.WebSocket;
|
||||
using Needlework.Net.Views.MainWindow;
|
||||
using System;
|
||||
using Needlework.Net.Views.Pages.About;
|
||||
using Needlework.Net.Views.Pages.Console;
|
||||
using Needlework.Net.Views.Pages.Endpoints;
|
||||
using Needlework.Net.Views.Pages.Home;
|
||||
using Needlework.Net.Views.Pages.WebSocket;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
using Splat.Serilog;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net;
|
||||
|
||||
public partial class App(IServiceProvider serviceProvider) : Application
|
||||
public partial class App : Application
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider = serviceProvider;
|
||||
|
||||
public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
@@ -31,17 +44,62 @@ public partial class App(IServiceProvider serviceProvider) : Application
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
RegisterValueConverters();
|
||||
RegisterAppServices();
|
||||
RegisterViews();
|
||||
RegisterViewModels();
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindowView()
|
||||
{
|
||||
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
||||
};
|
||||
desktop.MainWindow = new MainWindow() { DataContext = Locator.Current.GetService<IScreen>() };
|
||||
MainWindow = desktop.MainWindow;
|
||||
}
|
||||
|
||||
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private void RegisterValueConverters()
|
||||
{
|
||||
Locator.CurrentMutable.RegisterConstant<IBindingTypeConverter>(new NullableToVisibilityConverter());
|
||||
Locator.CurrentMutable.RegisterConstant<IBindingTypeConverter>(new EnumerableToVisibilityConverter());
|
||||
}
|
||||
|
||||
private static void RegisterViewModels()
|
||||
{
|
||||
Locator.CurrentMutable.RegisterConstant<PageBase>(new HomeViewModel());
|
||||
Locator.CurrentMutable.RegisterConstant<PageBase>(new EndpointsViewModel());
|
||||
Locator.CurrentMutable.RegisterConstant<PageBase>(new ConsoleViewModel());
|
||||
Locator.CurrentMutable.RegisterConstant<PageBase>(new WebSocketViewModel());
|
||||
Locator.CurrentMutable.RegisterConstant<PageBase>(new AboutViewModel());
|
||||
Locator.CurrentMutable.RegisterConstant<IScreen>(new MainWindowViewModel());
|
||||
}
|
||||
|
||||
private static void RegisterViews()
|
||||
{
|
||||
Locator.CurrentMutable.Register<IViewFor<LibraryViewModel>>(() => new LibraryView());
|
||||
Locator.CurrentMutable.Register<IViewFor<NotificationViewModel>>(() => new NotificationView());
|
||||
Locator.CurrentMutable.Register<IViewFor<EventViewModel>>(() => new EventView());
|
||||
Locator.CurrentMutable.Register<IViewFor<EndpointTabListViewModel>>(() => new EndpointTabListView());
|
||||
Locator.CurrentMutable.Register<IViewFor<EndpointTabItemContentViewModel>>(() => new EndpointTabItemContentView());
|
||||
Locator.CurrentMutable.Register<IViewFor<EndpointSearchDetailsViewModel>>(() => new EndpointSearchDetailsView());
|
||||
Locator.CurrentMutable.Register<IViewFor<PluginViewModel>>(() => new PluginView());
|
||||
Locator.CurrentMutable.Register<IViewFor<PropertyClassViewModel>>(() => new PropertyClassView());
|
||||
Locator.CurrentMutable.Register<IViewFor<PathOperationViewModel>>(() => new PathOperationView());
|
||||
|
||||
Locator.CurrentMutable.RegisterConstant<IViewFor<HomeViewModel>>(new HomePage());
|
||||
Locator.CurrentMutable.RegisterConstant<IViewFor<EndpointsViewModel>>(new EndpointsPage());
|
||||
Locator.CurrentMutable.RegisterConstant<IViewFor<ConsoleViewModel>>(new ConsolePage());
|
||||
Locator.CurrentMutable.RegisterConstant<IViewFor<WebSocketViewModel>>(new WebSocketPage());
|
||||
Locator.CurrentMutable.RegisterConstant<IViewFor<AboutViewModel>>(new AboutPage());
|
||||
}
|
||||
|
||||
private static void RegisterAppServices()
|
||||
{
|
||||
Locator.CurrentMutable.UseSerilogFullLogger(Logger.Setup());
|
||||
Locator.CurrentMutable.RegisterConstant<IFlurlClientCache>(new FlurlClientCache()
|
||||
.Add("GithubClient", "https://api.github.com")
|
||||
.Add("GithubUserContentClient", "https://raw.githubusercontent.com"));
|
||||
Locator.CurrentMutable.RegisterConstant<NotificationService>(new NotificationService());
|
||||
Locator.CurrentMutable.RegisterConstant<DataSource>(new DataSource());
|
||||
}
|
||||
}
|
||||
BIN
Needlework.Net/Assets/Users/aoshiw.png
Normal file
BIN
Needlework.Net/Assets/Users/aoshiw.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 5.1 KiB |
BIN
Needlework.Net/Assets/Users/sylv.jpg
Normal file
BIN
Needlework.Net/Assets/Users/sylv.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 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"}]
|
||||
71
Needlework.Net/Controls/UserCard.axaml
Normal file
71
Needlework.Net/Controls/UserCard.axaml
Normal file
@@ -0,0 +1,71 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
xmlns:controls="using:Needlework.Net.Controls">
|
||||
<Design.PreviewWith>
|
||||
<WrapPanel>
|
||||
<controls:UserCard
|
||||
Width="300"
|
||||
Height="400"
|
||||
UserImage="/Assets/Users/blossomishymae.png"
|
||||
UserName="estrogen elf"
|
||||
UserGithub="BlossomiShymae">
|
||||
Needlework.Net is the .NET rewrite of Needlework. This tool was made to help others with LCU and Game Client development. Feel free to ask any questions
|
||||
or help contribute to the project! Made with love. 💜
|
||||
</controls:UserCard>
|
||||
</WrapPanel>
|
||||
</Design.PreviewWith>
|
||||
<Style Selector="controls|UserCard">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid>
|
||||
<Border CornerRadius="16,16,16,16"
|
||||
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||
Margin="0 50 0 0"
|
||||
Padding="16 66 16 16">
|
||||
<Grid RowDefinitions="auto,auto,auto"
|
||||
ColumnDefinitions="*">
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Margin="0 0 0 4"
|
||||
Text="{TemplateBinding UserName}"/>
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="auto,auto"
|
||||
Margin="0 0 0 16">
|
||||
<Button Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
FontSize="20"
|
||||
Name="PART_GithubButton">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="8 0 0 0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{TemplateBinding UserGithub}"/>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
TextWrapping="WrapWithOverflow"
|
||||
Text="{TemplateBinding Content}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border CornerRadius="100"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Margin="{TemplateBinding UserImageMargin}"
|
||||
ClipToBounds="True">
|
||||
<Image Source="{TemplateBinding UserImage}"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
91
Needlework.Net/Controls/UserCard.axaml.cs
Normal file
91
Needlework.Net/Controls/UserCard.axaml.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Needlework.Net.Controls;
|
||||
|
||||
[TemplatePart("PART_GithubButton", typeof(Button))]
|
||||
public partial class UserCard : ContentControl
|
||||
{
|
||||
private Button? _githubButton;
|
||||
|
||||
public UserCard()
|
||||
{
|
||||
UserImageMargin = new(0, !double.IsNaN(Height) ? 100 - Height : 0, 0, 0);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IImage?> UserImageProperty =
|
||||
AvaloniaProperty.Register<UserCard, IImage?>(nameof(UserImage), defaultValue: null);
|
||||
|
||||
public IImage? UserImage
|
||||
{
|
||||
get { return GetValue(UserImageProperty); }
|
||||
set { SetValue(UserImageProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string?> UserNameProperty =
|
||||
AvaloniaProperty.Register<UserCard, string?>(nameof(UserName), defaultValue: null);
|
||||
|
||||
public string? UserName
|
||||
{
|
||||
get { return GetValue(UserNameProperty); }
|
||||
set { SetValue(UserNameProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string?> UserGithubProperty =
|
||||
AvaloniaProperty.Register<UserCard, string?>(nameof(UserGithub), defaultValue: null);
|
||||
|
||||
public string? UserGithub
|
||||
{
|
||||
get { return GetValue(UserGithubProperty); }
|
||||
set { SetValue(UserGithubProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DirectProperty<UserCard, Thickness> UserImageMarginProperty =
|
||||
AvaloniaProperty.RegisterDirect<UserCard, Thickness>(nameof(UserImageMargin), o => o.UserImageMargin);
|
||||
|
||||
private Thickness _userImageMargin = new(0, 0, 0, 0);
|
||||
|
||||
public Thickness UserImageMargin
|
||||
{
|
||||
get { return _userImageMargin; }
|
||||
private set { SetAndRaise(UserImageMarginProperty, ref _userImageMargin, value); }
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
SizeChanged += UserCard_SizeChanged;
|
||||
|
||||
if (_githubButton != null)
|
||||
{
|
||||
_githubButton.Click -= GithubButton_Click;
|
||||
}
|
||||
|
||||
_githubButton = e.NameScope.Find("PART_GithubButton") as Button;
|
||||
|
||||
if (_githubButton != null)
|
||||
{
|
||||
_githubButton.Click += GithubButton_Click;
|
||||
}
|
||||
}
|
||||
|
||||
private void UserCard_SizeChanged(object? sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UserImageMargin = new(0, !double.IsNaN(e.NewSize.Height) ? 100 - e.NewSize.Height : 0, 0, 0);
|
||||
}
|
||||
|
||||
private void GithubButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo($"https://github.com/{UserGithub}") { UseShellExecute = true }
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
33
Needlework.Net/Converters/EnumerableToVisibilityConverter.cs
Normal file
33
Needlework.Net/Converters/EnumerableToVisibilityConverter.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Converters
|
||||
{
|
||||
public class EnumerableToVisibilityConverter : IBindingTypeConverter
|
||||
{
|
||||
public int GetAffinityForObjects(Type fromType, Type toType)
|
||||
{
|
||||
if (typeof(IEnumerable<object>).IsAssignableFrom(fromType) && toType == typeof(bool))
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = from is IEnumerable<object> values && values.Any();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Needlework.Net/Converters/NullableToVisibilityConverter.cs
Normal file
23
Needlework.Net/Converters/NullableToVisibilityConverter.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.Converters
|
||||
{
|
||||
public class NullableToVisibilityConverter : IBindingTypeConverter
|
||||
{
|
||||
public int GetAffinityForObjects(Type fromType, Type toType)
|
||||
{
|
||||
if (typeof(object).IsAssignableFrom(fromType) && toType == typeof(bool))
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result)
|
||||
{
|
||||
result = from != null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Needlework.Net/DataSource.cs
Normal file
53
Needlework.Net/DataSource.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using FastCache;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Microsoft.OpenApi.Readers;
|
||||
using Needlework.Net.Models;
|
||||
using Splat;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net
|
||||
{
|
||||
public class DataSource : IEnableLogger
|
||||
{
|
||||
private readonly OpenApiStreamReader _reader = new();
|
||||
|
||||
private readonly IFlurlClient _githubUserContentClient;
|
||||
|
||||
public DataSource(IFlurlClientCache? clients = null)
|
||||
{
|
||||
_githubUserContentClient = clients?.Get("GithubUserContentClient") ?? Locator.Current.GetService<IFlurlClientCache>()!.Get("GithubUserContentClient")!;
|
||||
}
|
||||
|
||||
public async Task<Document> GetLcuSchemaDocumentAsync()
|
||||
{
|
||||
if (Cached<Document>.TryGet(nameof(GetLcuSchemaDocumentAsync), out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var lcuSchemaStream = await _githubUserContentClient.Request("/dysolix/hasagi-types/main/swagger.json")
|
||||
.GetStreamAsync();
|
||||
var lcuSchemaRaw = _reader.Read(lcuSchemaStream, out var _);
|
||||
var document = new Document(lcuSchemaRaw);
|
||||
|
||||
return cached.Save(document, TimeSpan.FromMinutes(60));
|
||||
}
|
||||
|
||||
public async Task<Document> GetLolClientDocumentAsync()
|
||||
{
|
||||
if (Cached<Document>.TryGet(nameof(GetLolClientDocumentAsync), out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var lolClientStream = await _githubUserContentClient.Request("/AlsoSylv/Irelia/refs/heads/master/schemas/game_schema.json")
|
||||
.GetStreamAsync();
|
||||
var lolClientRaw = _reader.Read(lolClientStream, out var _);
|
||||
var document = new Document(lolClientRaw);
|
||||
|
||||
return cached.Save(document, TimeSpan.FromMinutes(60));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Needlework.Net.Extensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddSingletonsFromAssemblies<T>(this ServiceCollection services)
|
||||
{
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => !p.IsAbstract && typeof(T).IsAssignableFrom(p));
|
||||
|
||||
foreach (var type in types) services.AddSingleton(typeof(T), type);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Needlework.Net/Logger.cs
Normal file
28
Needlework.Net/Logger.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Needlework.Net
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
public static ILogger Setup()
|
||||
{
|
||||
var logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.File("Logs/debug-.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);
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
public static void LogFatal(UnhandledExceptionEventArgs e)
|
||||
{
|
||||
File.AppendAllText($"Logs/fatal-{DateTime.Now:yyyyMMdd}.log", e.ExceptionObject.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Models;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class DataReadyMessage(OpenApiDocumentWrapper wrapper) : ValueChangedMessage<OpenApiDocumentWrapper>(wrapper)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Models;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class DataRequestMessage : RequestMessage<OpenApiDocumentWrapper>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class HostDocumentRequestMessage : RequestMessage<OpenApiDocument>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.ViewModels.MainWindow;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class InfoBarUpdateMessage(InfoBarViewModel vm) : ValueChangedMessage<InfoBarViewModel>(vm)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class OopsiesDialogRequestedMessage(string text) : ValueChangedMessage<string>(text)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class ResponseUpdatedMessage(string data) : ValueChangedMessage<string>(data)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Models;
|
||||
|
||||
public class OpenApiDocumentWrapper
|
||||
public class Document
|
||||
{
|
||||
internal OpenApiDocument OpenApiDocument { get; }
|
||||
|
||||
@@ -13,7 +14,7 @@ public class OpenApiDocumentWrapper
|
||||
|
||||
public List<string> Paths => [.. OpenApiDocument.Paths.Keys];
|
||||
|
||||
public OpenApiDocumentWrapper(OpenApiDocument openApiDocument)
|
||||
public Document(OpenApiDocument openApiDocument)
|
||||
{
|
||||
OpenApiDocument = openApiDocument;
|
||||
var plugins = new SortedDictionary<string, List<PathOperation>>();
|
||||
@@ -35,10 +36,10 @@ public class OpenApiDocumentWrapper
|
||||
{
|
||||
pluginsKey = "default";
|
||||
if (plugins.TryGetValue(pluginsKey, out var p))
|
||||
p.Add(new(method.ToString(), path, operation));
|
||||
p.Add(new(method.ToString(), path, pluginsKey, operation));
|
||||
else
|
||||
{
|
||||
operations.Add(new(method.ToString(), path, operation));
|
||||
operations.Add(new(method.ToString(), path, pluginsKey, operation));
|
||||
plugins[pluginsKey] = operations;
|
||||
}
|
||||
}
|
||||
@@ -46,19 +47,16 @@ public class OpenApiDocumentWrapper
|
||||
{
|
||||
foreach (var tag in operation.Tags)
|
||||
{
|
||||
var lowercaseTag = tag.Name.ToLower();
|
||||
if (lowercaseTag == "plugins")
|
||||
if (tag.Name == "plugins")
|
||||
continue;
|
||||
else if (lowercaseTag.Contains("plugin "))
|
||||
pluginsKey = lowercaseTag.Replace("plugin ", "");
|
||||
else
|
||||
pluginsKey = lowercaseTag;
|
||||
pluginsKey = tag.Name;
|
||||
|
||||
if (plugins.TryGetValue(pluginsKey, out var p))
|
||||
p.Add(new(method.ToString(), path, operation));
|
||||
p.Add(new(method.ToString(), path, tag.Name, operation));
|
||||
else
|
||||
{
|
||||
operations.Add(new(method.ToString(), path, operation));
|
||||
operations.Add(new(method.ToString(), path, tag.Name, operation));
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,6 @@ namespace Needlework.Net.Models
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; } = string.Empty;
|
||||
|
||||
public bool IsLatest(int version) => int.Parse(TagName.Replace(".", "")) > version;
|
||||
public bool IsLatest(string assemblyVersion) => int.Parse(TagName.Replace(".", string.Empty)) > int.Parse(assemblyVersion.Replace(".", string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
9
Needlework.Net/Models/Notification.cs
Normal file
9
Needlework.Net/Models/Notification.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.Models
|
||||
{
|
||||
public record Notification(string Title, string Message, InfoBarSeverity InfoBarSeverity, TimeSpan? Duration = null, string? Url = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,4 @@ using Microsoft.OpenApi.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);
|
||||
@@ -1,23 +0,0 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi.Readers;
|
||||
|
||||
namespace Needlework.Net.Models;
|
||||
|
||||
public static class Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the OpenApi document of the LCU schema. Provided by dysolix.
|
||||
/// </summary>
|
||||
/// <param name="httpClient"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<OpenApiDocument> GetOpenApiDocumentAsync(HttpClient httpClient)
|
||||
{
|
||||
var stream = await httpClient.GetStreamAsync("https://raw.githubusercontent.com/dysolix/hasagi-types/main/swagger.json");
|
||||
|
||||
var document = new OpenApiStreamReader().Read(stream, out var _);
|
||||
|
||||
return document;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
@@ -11,38 +11,58 @@
|
||||
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
||||
<AssemblyVersion>0.8.1.0</AssemblyVersion>
|
||||
<AssemblyVersion>0.13.0.0</AssemblyVersion>
|
||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.1" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
|
||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" />
|
||||
<PackageReference Include="BlossomiShymae.GrrrLCU" Version="0.14.0" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.1" />
|
||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.3.0" />
|
||||
<PackageReference Include="BlossomiShymae.Briar" Version="0.2.0" />
|
||||
<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.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="FastCache.Cached" Version="1.8.2" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="2.3.0" />
|
||||
<PackageReference Include="Flurl" Version="4.0.0" />
|
||||
<PackageReference Include="Flurl.Http" Version="4.0.2" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.4.1" />
|
||||
<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.24" />
|
||||
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.24" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia" Version="9.6.2" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
|
||||
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.2.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="Splat.Serilog" Version="15.3.1" />
|
||||
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.69" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaXaml Remove="Utilities\**" />
|
||||
<Compile Remove="Utilities\**" />
|
||||
<EmbeddedResource Remove="Utilities\**" />
|
||||
<None Remove="Utilities\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Views\AboutView.axaml" />
|
||||
@@ -52,19 +72,36 @@
|
||||
<Compile Update="Controls\BusyArea.axaml.cs">
|
||||
<DependentUpon>BusyArea.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\MainWindow\MainWindowView.axaml.cs">
|
||||
<DependentUpon>MainWindowView.axaml</DependentUpon>
|
||||
<Compile Update="Views\MainWindow\MainWindow.axaml.cs">
|
||||
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\EndpointsNavigationView.axaml.cs">
|
||||
<DependentUpon>EndpointsNavigationView.axaml</DependentUpon>
|
||||
<Compile Update="Views\Pages\About\AboutPage.axaml.cs">
|
||||
<DependentUpon>AboutPage.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\EndpointView.axaml.cs">
|
||||
<DependentUpon>EndpointView.axaml</DependentUpon>
|
||||
<Compile Update="Views\Pages\Console\ConsolePage.axaml.cs">
|
||||
<DependentUpon>ConsolePage.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\EndpointTabListView.axaml.cs">
|
||||
<DependentUpon>EndpointTabListView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\EndpointTabItemContentView.axaml.cs">
|
||||
<DependentUpon>EndpointTabItemContentView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\EndpointsPage.axaml.cs">
|
||||
<DependentUpon>EndpointsPage.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\PluginView.axaml.cs">
|
||||
<DependentUpon>PluginView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Home\HomePage.axaml.cs">
|
||||
<DependentUpon>HomePage.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\WebSocket\WebSocketPage.axaml.cs">
|
||||
<DependentUpon>WebSocketPage.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\Users\" />
|
||||
<Folder Include="Utilities\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
using Avalonia;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.MainWindow;
|
||||
using Needlework.Net.ViewModels.Pages;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Projektanker.Icons.Avalonia;
|
||||
using Projektanker.Icons.Avalonia.FontAwesome;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Needlework.Net;
|
||||
|
||||
@@ -31,28 +26,17 @@ class Program
|
||||
IconProvider.Current
|
||||
.Register<FontAwesomeIconProvider>();
|
||||
|
||||
return AppBuilder.Configure(() => new App(BuildServices()))
|
||||
return AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
|
||||
private static IServiceProvider BuildServices()
|
||||
{
|
||||
var builder = new ServiceCollection();
|
||||
|
||||
builder.AddSingleton<MainWindowViewModel>();
|
||||
builder.AddSingleton<DialogService>();
|
||||
builder.AddSingletonsFromAssemblies<PageBase>();
|
||||
|
||||
builder.AddHttpClient();
|
||||
|
||||
var services = builder.BuildServiceProvider();
|
||||
return services;
|
||||
.With(new Win32PlatformOptions { CompositionMode = [Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition] })
|
||||
.With(new MacOSPlatformOptions { ShowInDock = true, })
|
||||
.LogToTrace()
|
||||
.UseReactiveUI();
|
||||
}
|
||||
|
||||
private static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
File.WriteAllText($"errorlog-{DateTime.Now:HHmmssfff}", e.ExceptionObject.ToString());
|
||||
Logger.LogFatal(e);
|
||||
}
|
||||
}
|
||||
|
||||
19
Needlework.Net/Services/NotificationService.cs
Normal file
19
Needlework.Net/Services/NotificationService.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Needlework.Net.Models;
|
||||
using System;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace Needlework.Net.Services
|
||||
{
|
||||
public class NotificationService
|
||||
{
|
||||
private readonly Subject<Notification> _notificationSubject = new();
|
||||
|
||||
public IObservable<Notification> Notifications { get { return _notificationSubject; } }
|
||||
|
||||
public void Notify(string title, string message, InfoBarSeverity severity, TimeSpan? duration = null, string? url = null)
|
||||
{
|
||||
_notificationSubject.OnNext(new Notification(title, message, severity, duration, url));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Needlework.Net
|
||||
{
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
private readonly Dictionary<object, Control> _controlCache = [];
|
||||
|
||||
public Control Build(object? data)
|
||||
{
|
||||
var name = data?.GetType().Name;
|
||||
if (name is null)
|
||||
{
|
||||
return new TextBlock { Text = "Data is null or has no name." };
|
||||
}
|
||||
if (!name.Contains("ViewModel"))
|
||||
{
|
||||
return new TextBlock { Text = "Data name must end with ViewModel." };
|
||||
}
|
||||
|
||||
name = name.Replace("ViewModel", "View");
|
||||
var type = Assembly.GetExecutingAssembly()
|
||||
.GetTypes()
|
||||
.Where(t => t.Name == name)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (type is null)
|
||||
{
|
||||
return new TextBlock { Text = $"No view for {name}." };
|
||||
}
|
||||
|
||||
if (!_controlCache.TryGetValue(data!, out var res))
|
||||
{
|
||||
res ??= (Control)Activator.CreateInstance(type)!;
|
||||
_controlCache[data!] = res;
|
||||
}
|
||||
|
||||
res.DataContext = data;
|
||||
return res;
|
||||
}
|
||||
|
||||
public bool Match(object? data) => data is INotifyPropertyChanged;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow;
|
||||
|
||||
public partial class InfoBarViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _title;
|
||||
[ObservableProperty] private bool _isOpen;
|
||||
[ObservableProperty] private string _message;
|
||||
[ObservableProperty] private InfoBarSeverity _severity;
|
||||
[ObservableProperty] private TimeSpan _duration;
|
||||
[ObservableProperty] private Control? _actionButton;
|
||||
|
||||
public InfoBarViewModel(string title, bool isOpen, string message, InfoBarSeverity severity, TimeSpan duration, Control? actionButton = null)
|
||||
{
|
||||
_title = title;
|
||||
_isOpen = isOpen;
|
||||
_message = message;
|
||||
_severity = severity;
|
||||
_duration = duration;
|
||||
_actionButton = actionButton;
|
||||
}
|
||||
}
|
||||
@@ -1,157 +1,162 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using BlossomiShymae.Briar;
|
||||
using BlossomiShymae.Briar.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Messages;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.Pages;
|
||||
using Needlework.Net.Views.MainWindow;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using Splat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow;
|
||||
|
||||
public partial class MainWindowViewModel
|
||||
: ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
|
||||
: ReactiveObject, IScreen, IEnableLogger
|
||||
{
|
||||
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
|
||||
[NotifyPropertyChangedFor(nameof(CurrentPage))]
|
||||
[ObservableProperty] private NavigationViewItem _selectedMenuItem;
|
||||
public PageBase CurrentPage => (PageBase)SelectedMenuItem.Tag!;
|
||||
private readonly IEnumerable<PageBase> _pages;
|
||||
|
||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||
[ObservableProperty] private bool _isUpdateShown = false;
|
||||
private readonly IFlurlClient _githubClient;
|
||||
|
||||
public HttpClient HttpClient { get; }
|
||||
public DialogService DialogService { get; }
|
||||
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
|
||||
public OpenApiDocument? HostDocument { get; set; }
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
private readonly DataSource _dataSource;
|
||||
|
||||
[ObservableProperty] private ObservableCollection<InfoBarViewModel> _infoBarItems = [];
|
||||
private readonly IDisposable _checkForUpdatesDisposable;
|
||||
|
||||
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService)
|
||||
private readonly IDisposable _checkForSchemaVersionDisposable;
|
||||
|
||||
public MainWindowViewModel(IEnumerable<PageBase>? pages = null, IFlurlClientCache? clients = null, NotificationService? notificationService = null, DataSource? dataSource = null)
|
||||
{
|
||||
MenuItems = new AvaloniaList<NavigationViewItem>(pages
|
||||
_pages = pages ?? Locator.Current.GetServices<PageBase>();
|
||||
_githubClient = clients?.Get("GithubClient") ?? Locator.Current.GetService<IFlurlClientCache>()!.Get("GithubClient");
|
||||
_notificationService = notificationService ?? Locator.Current.GetService<NotificationService>()!;
|
||||
_dataSource = dataSource ?? Locator.Current.GetService<DataSource>()!;
|
||||
|
||||
PageItems = _pages
|
||||
.OrderBy(p => p.Index)
|
||||
.ThenBy(p => p.DisplayName)
|
||||
.Select(p => new NavigationViewItem()
|
||||
{
|
||||
Content = p.DisplayName,
|
||||
Tag = p,
|
||||
IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") }
|
||||
}));
|
||||
SelectedMenuItem = MenuItems[0];
|
||||
.Select(ToNavigationViewItem)
|
||||
.ToList();
|
||||
|
||||
HttpClient = httpClient;
|
||||
DialogService = dialogService;
|
||||
SelectedPageItem = PageItems.First();
|
||||
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
this.WhenAnyValue(x => x.SelectedPageItem)
|
||||
.Subscribe(x => Router.Navigate.Execute((IRoutableViewModel)x.Tag!));
|
||||
|
||||
Task.Run(FetchDataAsync);
|
||||
new Thread(ProcessEvents) { IsBackground = true }.Start();
|
||||
_notificationService.Notifications.Subscribe(async notification =>
|
||||
{
|
||||
var vm = new NotificationViewModel(notification);
|
||||
Notifications.Add(vm);
|
||||
await Task.Delay(notification.Duration ?? TimeSpan.FromSeconds(10));
|
||||
Notifications.Remove(vm);
|
||||
});
|
||||
|
||||
CheckForUpdatesCommand.ThrownExceptions.Subscribe(ex =>
|
||||
{
|
||||
var message = "Failed to check for updates. Please check your internet connection or try again later.";
|
||||
this.Log()
|
||||
.Error(ex, message);
|
||||
_notificationService.Notify("Needlework.Net", message, InfoBarSeverity.Error);
|
||||
_checkForUpdatesDisposable?.Dispose();
|
||||
});
|
||||
|
||||
_checkForUpdatesDisposable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(10))
|
||||
.Select(time => Unit.Default)
|
||||
.InvokeCommand(this, x => x.CheckForUpdatesCommand);
|
||||
|
||||
CheckForSchemaVersionCommand.ThrownExceptions.Subscribe(ex =>
|
||||
{
|
||||
var message = "Failed to check for schema version. Please check your internet connection or try again later.";
|
||||
this.Log()
|
||||
.Error(ex, message);
|
||||
_notificationService.Notify("Needlework.Net", message, InfoBarSeverity.Error);
|
||||
_checkForSchemaVersionDisposable?.Dispose();
|
||||
});
|
||||
|
||||
_checkForSchemaVersionDisposable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(10))
|
||||
.Select(time => Unit.Default)
|
||||
.InvokeCommand(this, x => x.CheckForSchemaVersionCommand);
|
||||
}
|
||||
|
||||
private void ProcessEvents(object? obj)
|
||||
{
|
||||
while (!IsUpdateShown)
|
||||
{
|
||||
Task.Run(CheckLatestVersionAsync);
|
||||
[Reactive]
|
||||
private RoutingState _router = new();
|
||||
|
||||
Thread.Sleep(TimeSpan.FromMinutes(10)); // Avoid tripping unauthenticated rate limits
|
||||
[Reactive]
|
||||
private ObservableCollection<NotificationViewModel> _notifications = [];
|
||||
|
||||
[Reactive]
|
||||
private NavigationViewItem _selectedPageItem;
|
||||
|
||||
public List<NavigationViewItem> PageItems = [];
|
||||
|
||||
public bool IsSchemaVersionChecked { get; private set; } = false;
|
||||
|
||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||
|
||||
private NavigationViewItem ToNavigationViewItem(PageBase page) => new()
|
||||
{
|
||||
Content = page.DisplayName,
|
||||
Tag = page,
|
||||
IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{page.Icon}.png") }
|
||||
};
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task CheckForUpdatesAsync()
|
||||
{
|
||||
var release = await _githubClient
|
||||
.Request("/repos/BlossomiShymae/Needlework.Net/releases/latest")
|
||||
.WithHeader("User-Agent", $"Needlework.Net/{Version}")
|
||||
.GetJsonAsync<GithubRelease>();
|
||||
|
||||
if (release.IsLatest(Version))
|
||||
{
|
||||
this.Log()
|
||||
.Info("New version available: {TagName}", release.TagName);
|
||||
_notificationService.Notify("Needlework.Net", $"New version available: {release.TagName}", InfoBarSeverity.Informational, null, "https://github.com/BlossomiShymae/Needlework.Net/releases/latest");
|
||||
_checkForUpdatesDisposable?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckLatestVersionAsync()
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task CheckForSchemaVersionAsync()
|
||||
{
|
||||
try
|
||||
if (!ProcessFinder.IsPortOpen()) return;
|
||||
|
||||
var lcuSchemaDocument = await _dataSource.GetLcuSchemaDocumentAsync();
|
||||
var client = Connector.GetLcuHttpClientInstance();
|
||||
var currentSemVer = lcuSchemaDocument.Info.Version.Split('.');
|
||||
var systemBuild = await client.GetFromJsonAsync<SystemBuild>("/system/v1/builds") ?? throw new NullReferenceException();
|
||||
var latestSemVer = systemBuild.Version.Split('.');
|
||||
|
||||
if (!IsSchemaVersionChecked)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/BlossomiShymae/Needlework.Net/releases/latest");
|
||||
request.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("Needlework.Net", Version));
|
||||
|
||||
var response = await HttpClient.SendAsync(request);
|
||||
var release = await response.Content.ReadFromJsonAsync<GithubRelease>();
|
||||
if (release == null) return;
|
||||
|
||||
var currentVersion = int.Parse(Version.Replace(".", ""));
|
||||
|
||||
if (release.IsLatest(currentVersion))
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ShowInfoBarAsync(new("Needlework.Net Update", true, $"There is a new version available: {release.TagName}.", InfoBarSeverity.Informational, TimeSpan.FromSeconds(10), new Avalonia.Controls.Button()
|
||||
{
|
||||
Command = OpenUrlCommand,
|
||||
CommandParameter = "https://github.com/BlossomiShymae/Needlework.Net/releases",
|
||||
Content = "Download"
|
||||
}));
|
||||
IsUpdateShown = true;
|
||||
});
|
||||
}
|
||||
this.Log()
|
||||
.Info("LCU Schema (current): {Version}", lcuSchemaDocument.Info.Version);
|
||||
this.Log()
|
||||
.Info("LCU Schema (latest): {Version}", systemBuild.Version);
|
||||
IsSchemaVersionChecked = true;
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
private async Task FetchDataAsync()
|
||||
{
|
||||
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
||||
HostDocument = document;
|
||||
var handler = new OpenApiDocumentWrapper(document);
|
||||
OpenApiDocumentWrapper = handler;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
public void Receive(DataRequestMessage message)
|
||||
{
|
||||
message.Reply(OpenApiDocumentWrapper!);
|
||||
}
|
||||
|
||||
public void Receive(HostDocumentRequestMessage message)
|
||||
{
|
||||
message.Reply(HostDocument!);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process()
|
||||
bool isVersionMatching = currentSemVer[0] == latestSemVer[0] && currentSemVer[1] == latestSemVer[1]; // Compare major and minor versions
|
||||
if (!isVersionMatching)
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(url)
|
||||
{
|
||||
UseShellExecute = true
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
|
||||
public void Receive(InfoBarUpdateMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(async () => await ShowInfoBarAsync(message.Value));
|
||||
}
|
||||
|
||||
private async Task ShowInfoBarAsync(InfoBarViewModel vm)
|
||||
{
|
||||
InfoBarItems.Add(vm);
|
||||
await Task.Delay(vm.Duration);
|
||||
InfoBarItems.Remove(vm);
|
||||
}
|
||||
|
||||
public void Receive(OopsiesDialogRequestedMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () => await DialogService.ShowAsync<OopsiesDialog>(message.Value));
|
||||
this.Log()
|
||||
.Warn("LCU Schema version mismatch: Current {CurrentVersion}, Latest {LatestVersion}", lcuSchemaDocument.Info.Version, systemBuild.Version);
|
||||
_notificationService.Notify("Needlework.Net", $"LCU Schema is possibly outdated compared to latest system build. Consider submitting a pull request on dysolix/hasagi-types.\nCurrent: {string.Join(".", currentSemVer)}\nLatest: {string.Join(".", latestSemVer)}", InfoBarSeverity.Warning, null, "https://github.com/dysolix/hasagi-types#updating-the-types");
|
||||
_checkForSchemaVersionDisposable?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow
|
||||
{
|
||||
public partial class NotificationViewModel : ReactiveObject
|
||||
{
|
||||
private IObservable<bool> _canExecute;
|
||||
|
||||
public NotificationViewModel(Needlework.Net.Models.Notification notification)
|
||||
{
|
||||
Notification = notification;
|
||||
|
||||
_canExecute = this.WhenAnyValue(x => x.Notification.Url)
|
||||
.Select(url => !string.IsNullOrEmpty(url));
|
||||
|
||||
_isButtonVisibleHelper = _canExecute.ToProperty(this, x => x.IsButtonVisible);
|
||||
}
|
||||
|
||||
[ObservableAsProperty]
|
||||
private bool _isButtonVisible = false;
|
||||
|
||||
public Needlework.Net.Models.Notification Notification { get; }
|
||||
|
||||
[ReactiveCommand(CanExecute = nameof(_canExecute))]
|
||||
public void OpenUrl()
|
||||
{
|
||||
var process = new Process() { StartInfo = new() { UseShellExecute = true } };
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Needlework.Net/ViewModels/Pages/About/AboutViewModel.cs
Normal file
16
Needlework.Net/ViewModels/Pages/About/AboutViewModel.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.About;
|
||||
|
||||
public partial class AboutViewModel : PageBase
|
||||
{
|
||||
public AboutViewModel(IScreen? screen = null) : base("About", "info-circle")
|
||||
{
|
||||
HostScreen = screen ?? Locator.Current.GetService<IScreen>()!;
|
||||
}
|
||||
|
||||
public override string? UrlPathSegment => "about";
|
||||
|
||||
public override IScreen HostScreen { get; }
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages;
|
||||
|
||||
public partial class AboutViewModel : PageBase
|
||||
{
|
||||
public HttpClient HttpClient { get; }
|
||||
|
||||
public AboutViewModel(HttpClient httpClient) : base("About", "info-circle")
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
66
Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs
Normal file
66
Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using DynamicData;
|
||||
using Needlework.Net.ViewModels.Shared;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using Splat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Console;
|
||||
|
||||
public partial class ConsoleViewModel : PageBase
|
||||
{
|
||||
private readonly DataSource _dataSource;
|
||||
|
||||
public ConsoleViewModel(IScreen? screen = null, DataSource? dataSource = null) : base("Console", "terminal", -200)
|
||||
{
|
||||
_dataSource = dataSource ?? Locator.Current.GetService<DataSource>()!;
|
||||
_request = new(Endpoints.Tab.LCU);
|
||||
|
||||
HostScreen = screen ?? Locator.Current.GetService<IScreen>()!;
|
||||
|
||||
GetRequestPathsCommand.Subscribe(paths =>
|
||||
{
|
||||
RequestPaths.Clear();
|
||||
RequestPaths.AddRange(paths);
|
||||
IsBusy = false;
|
||||
});
|
||||
GetRequestPathsCommand.ThrownExceptions.Subscribe(ex =>
|
||||
{
|
||||
this.Log()
|
||||
.Error(ex, "Failed to load request paths from LCU Schema document.");
|
||||
IsBusy = false;
|
||||
});
|
||||
}
|
||||
|
||||
public ObservableCollection<string> RequestMethods { get; } = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"];
|
||||
|
||||
public override string? UrlPathSegment => "console";
|
||||
|
||||
public override ReactiveUI.IScreen HostScreen { get; }
|
||||
|
||||
[Reactive]
|
||||
private ObservableCollection<string> _requestPaths = [];
|
||||
|
||||
[Reactive]
|
||||
private bool _isBusy = true;
|
||||
|
||||
[Reactive]
|
||||
private RequestViewModel _request;
|
||||
|
||||
|
||||
[ReactiveCommand]
|
||||
public async Task<List<string>> GetRequestPathsAsync()
|
||||
{
|
||||
var document = await _dataSource.GetLcuSchemaDocumentAsync();
|
||||
return document.Paths;
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task SendRequestAsync()
|
||||
{
|
||||
await Request.ExecuteAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.ViewModels.Shared;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages;
|
||||
|
||||
public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
|
||||
{
|
||||
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
||||
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
[ObservableProperty] private LcuRequestViewModel _lcuRequest = new();
|
||||
|
||||
public ConsoleViewModel() : base("Console", "terminal", -200)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SendRequest()
|
||||
{
|
||||
await LcuRequest.ExecuteAsync();
|
||||
}
|
||||
|
||||
public void Receive(DataReadyMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
RequestPaths.Clear();
|
||||
RequestPaths.AddRange(message.Value.Paths);
|
||||
IsBusy = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Needlework.Net.Models;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints
|
||||
{
|
||||
public partial class EndpointSearchDetailsViewModel : ReactiveObject
|
||||
{
|
||||
private readonly Document _document;
|
||||
|
||||
private readonly Tab _tab;
|
||||
|
||||
private readonly Action<ReactiveObject> _onClicked;
|
||||
|
||||
|
||||
public EndpointSearchDetailsViewModel(Document document, Tab tab, Action<ReactiveObject> onClicked, string? plugin)
|
||||
{
|
||||
_document = document;
|
||||
_tab = tab;
|
||||
_onClicked = onClicked;
|
||||
_plugin = plugin;
|
||||
}
|
||||
|
||||
[Reactive]
|
||||
private string? _plugin;
|
||||
|
||||
[ReactiveCommand]
|
||||
private void OpenEndpoint()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_plugin)) return;
|
||||
_onClicked.Invoke(new PluginViewModel(_plugin, _document, _tab));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointTabItemContentViewModel : ReactiveObject
|
||||
{
|
||||
private readonly Action<string?, Guid> _onEndpointNavigation;
|
||||
|
||||
private readonly Tab _tab;
|
||||
|
||||
public EndpointTabItemContentViewModel(ObservableCollection<string> plugins, Action<string?, Guid> onEndpointNavigation, Models.Document document, Tab tab)
|
||||
{
|
||||
_activeViewModel = _endpointsViewModel = new EndpointTabListViewModel(plugins, OnClicked, document, tab);
|
||||
_onEndpointNavigation = onEndpointNavigation;
|
||||
_tab = tab;
|
||||
_title = GetTitle(tab);
|
||||
}
|
||||
|
||||
public Guid Guid { get; } = Guid.NewGuid();
|
||||
|
||||
[Reactive]
|
||||
private ReactiveObject _activeViewModel;
|
||||
|
||||
[Reactive]
|
||||
private ReactiveObject _endpointsViewModel;
|
||||
|
||||
[Reactive]
|
||||
private string _title;
|
||||
|
||||
private string GetTitle(Tab tab)
|
||||
{
|
||||
return tab switch
|
||||
{
|
||||
Tab.LCU => "LCU",
|
||||
Tab.GameClient => "Game Client",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
private void OnClicked(ReactiveObject viewModel)
|
||||
{
|
||||
ActiveViewModel = viewModel;
|
||||
if (viewModel is PluginViewModel endpoint)
|
||||
{
|
||||
Title = $"{GetTitle(_tab)} - {endpoint.Title}";
|
||||
_onEndpointNavigation.Invoke(endpoint.Title, Guid);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void GoBack()
|
||||
{
|
||||
ActiveViewModel = EndpointsViewModel;
|
||||
Title = GetTitle(_tab);
|
||||
_onEndpointNavigation.Invoke(null, Guid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints
|
||||
{
|
||||
public partial class EndpointTabItemViewModel : ReactiveObject
|
||||
{
|
||||
public EndpointTabItemViewModel(EndpointTabItemContentViewModel content, string? header = null, IconSource? iconSource = null, bool? selected = null)
|
||||
{
|
||||
_content = content;
|
||||
_header = header ?? string.Empty;
|
||||
_iconSource = iconSource ?? new SymbolIconSource() { Symbol = Symbol.Document, FontSize = 20.0, Foreground = Avalonia.Media.Brushes.White };
|
||||
_selected = selected ?? false;
|
||||
}
|
||||
|
||||
[Reactive]
|
||||
private string _header;
|
||||
|
||||
[Reactive]
|
||||
private IconSource _iconSource;
|
||||
|
||||
[Reactive]
|
||||
private bool _selected;
|
||||
|
||||
[Reactive]
|
||||
private EndpointTabItemContentViewModel _content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using DynamicData;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointTabListViewModel : ReactiveObject
|
||||
{
|
||||
public EndpointTabListViewModel(ObservableCollection<string> plugins, Action<ReactiveObject> onClicked, Models.Document document, Tab tab)
|
||||
{
|
||||
Plugins = new ObservableCollection<EndpointSearchDetailsViewModel>(plugins.Select(plugin => new EndpointSearchDetailsViewModel(document, tab, onClicked, plugin)));
|
||||
|
||||
this.WhenAnyValue(x => x.Search)
|
||||
.Subscribe(search =>
|
||||
{
|
||||
EndpointSearchDetails.Clear();
|
||||
if (string.IsNullOrEmpty(search))
|
||||
{
|
||||
EndpointSearchDetails.AddRange(
|
||||
plugins.Where(plugin => plugin.Contains(search, StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(plugin => new EndpointSearchDetailsViewModel(document, tab, onClicked, plugin)));
|
||||
}
|
||||
else
|
||||
{
|
||||
EndpointSearchDetails.AddRange(
|
||||
plugins.Select(plugin => new EndpointSearchDetailsViewModel(document, tab, onClicked, plugin)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ObservableCollection<EndpointSearchDetailsViewModel> Plugins { get; }
|
||||
|
||||
[Reactive]
|
||||
private ObservableCollection<EndpointSearchDetailsViewModel> _endpointSearchDetails = [];
|
||||
|
||||
[Reactive]
|
||||
private string _search = string.Empty;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointViewModel : ObservableObject
|
||||
{
|
||||
public string Endpoint { get; }
|
||||
public string Title => Endpoint;
|
||||
|
||||
|
||||
public IAvaloniaReadOnlyList<PathOperationViewModel> PathOperations { get; }
|
||||
[ObservableProperty] private PathOperationViewModel? _selectedPathOperation;
|
||||
|
||||
[ObservableProperty] private string? _search;
|
||||
public IAvaloniaList<PathOperationViewModel> FilteredPathOperations { get; }
|
||||
|
||||
public event EventHandler<string>? PathOperationSelected;
|
||||
|
||||
public EndpointViewModel(string endpoint)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
|
||||
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
|
||||
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x)));
|
||||
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
||||
}
|
||||
|
||||
partial void OnSearchChanged(string? value)
|
||||
{
|
||||
FilteredPathOperations.Clear();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
FilteredPathOperations.AddRange(PathOperations);
|
||||
return;
|
||||
}
|
||||
FilteredPathOperations.AddRange(PathOperations.Where(o => o.Path.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
||||
}
|
||||
|
||||
partial void OnSelectedPathOperationChanged(PathOperationViewModel? value)
|
||||
{
|
||||
if (value == null) return;
|
||||
PathOperationSelected?.Invoke(this, value.Operation.RequestTemplate ?? string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointsNavigationViewModel : ObservableObject
|
||||
{
|
||||
public Guid Guid { get; } = Guid.NewGuid();
|
||||
|
||||
[ObservableProperty] private ObservableObject _activeViewModel;
|
||||
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
||||
[ObservableProperty] private string _title = string.Empty;
|
||||
|
||||
private readonly Action<string?, Guid> _onEndpointNavigation;
|
||||
|
||||
public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation)
|
||||
{
|
||||
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked);
|
||||
_onEndpointNavigation = onEndpointNavigation;
|
||||
}
|
||||
|
||||
private void OnClicked(ObservableObject viewModel)
|
||||
{
|
||||
ActiveViewModel = viewModel;
|
||||
if (viewModel is EndpointViewModel endpoint)
|
||||
{
|
||||
Title = endpoint.Title;
|
||||
_onEndpointNavigation.Invoke(endpoint.Title, Guid);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void GoBack()
|
||||
{
|
||||
ActiveViewModel = EndpointsViewModel;
|
||||
Title = string.Empty;
|
||||
_onEndpointNavigation.Invoke(null, Guid);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessage>
|
||||
{
|
||||
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
|
||||
public IAvaloniaList<EndpointItem> Endpoints { get; } = new AvaloniaList<EndpointItem>();
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
|
||||
public EndpointsTabViewModel() : base("Endpoints", "list-alt", -500)
|
||||
{
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
}
|
||||
|
||||
public void Receive(DataReadyMessage message)
|
||||
{
|
||||
IsBusy = false;
|
||||
Plugins.Clear();
|
||||
Plugins.AddRange(message.Value.Plugins.Keys);
|
||||
|
||||
Dispatcher.UIThread.Post(AddEndpoint);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddEndpoint()
|
||||
{
|
||||
Endpoints.Add(new()
|
||||
{
|
||||
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation),
|
||||
Selected = true
|
||||
});
|
||||
}
|
||||
|
||||
private void OnEndpointNavigation(string? title, Guid guid)
|
||||
{
|
||||
foreach (var endpoint in Endpoints)
|
||||
{
|
||||
if (endpoint.Content.Guid.Equals(guid))
|
||||
{
|
||||
endpoint.Header = title ?? "Endpoints";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EndpointItem : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _header = "Endpoints";
|
||||
public IconSource IconSource { get; set; } = new SymbolIconSource() { Symbol = Symbol.Document, FontSize = 20.0, Foreground = Avalonia.Media.Brushes.White };
|
||||
public bool Selected { get; set; } = false;
|
||||
public required EndpointsNavigationViewModel Content { get; init; }
|
||||
}
|
||||
@@ -1,42 +1,82 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using DynamicData;
|
||||
using Needlework.Net.Models;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using Splat;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointsViewModel : ObservableObject
|
||||
public enum Tab
|
||||
{
|
||||
public IAvaloniaList<string> Plugins { get; }
|
||||
public IAvaloniaList<string> Query { get; }
|
||||
|
||||
[ObservableProperty] private string _search = string.Empty;
|
||||
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
||||
|
||||
public Action<ObservableObject> OnClicked { get; }
|
||||
|
||||
public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked)
|
||||
{
|
||||
Plugins = new AvaloniaList<string>(plugins);
|
||||
Query = new AvaloniaList<string>(plugins);
|
||||
OnClicked = onClicked;
|
||||
}
|
||||
|
||||
partial void OnSearchChanged(string value)
|
||||
{
|
||||
Query.Clear();
|
||||
if (!string.IsNullOrEmpty(Search))
|
||||
Query.AddRange(Plugins.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
||||
else
|
||||
Query.AddRange(Plugins);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenEndpoint(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return;
|
||||
|
||||
OnClicked.Invoke(new EndpointViewModel(value));
|
||||
}
|
||||
LCU,
|
||||
GameClient
|
||||
}
|
||||
|
||||
public partial class EndpointsViewModel : PageBase, IEnableLogger
|
||||
{
|
||||
public record Endpoint(Document Document, Tab Tab);
|
||||
|
||||
private readonly DataSource _dataSource;
|
||||
|
||||
public EndpointsViewModel(IScreen? screen = null, DataSource? dataSource = null) : base("Endpoints", "list-alt", -500)
|
||||
{
|
||||
_dataSource = dataSource ?? Locator.Current.GetService<DataSource>()!;
|
||||
|
||||
HostScreen = screen ?? Locator.Current.GetService<IScreen>()!;
|
||||
|
||||
GetEndpointCommand.Subscribe(endpoint =>
|
||||
{
|
||||
Plugins.Clear();
|
||||
Plugins.AddRange(endpoint.Document.Plugins.Keys);
|
||||
|
||||
var vm = new EndpointTabItemContentViewModel(Plugins, OnEndpointNavigation, endpoint.Document, endpoint.Tab);
|
||||
EndpointTabItems.Add(new(vm, vm.Title, null, true));
|
||||
IsBusy = false;
|
||||
});
|
||||
GetEndpointCommand.ThrownExceptions.Subscribe(ex =>
|
||||
{
|
||||
this.Log()
|
||||
.Error(ex, "Failed to get endpoint.");
|
||||
IsBusy = false;
|
||||
});
|
||||
}
|
||||
|
||||
public override string? UrlPathSegment => "endpoints";
|
||||
|
||||
public override ReactiveUI.IScreen HostScreen { get; }
|
||||
|
||||
[Reactive]
|
||||
public ObservableCollection<string> Plugins { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public ObservableCollection<EndpointTabItemViewModel> EndpointTabItems { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
private bool _isBusy = true;
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task<Endpoint> GetEndpointAsync(Tab tab)
|
||||
{
|
||||
return tab switch
|
||||
{
|
||||
Tab.LCU => new(await _dataSource.GetLcuSchemaDocumentAsync(), tab),
|
||||
Tab.GameClient => new(await _dataSource.GetLolClientDocumentAsync(), tab),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
private void OnEndpointNavigation(string? title, Guid guid)
|
||||
{
|
||||
foreach (var endpoint in EndpointTabItems)
|
||||
{
|
||||
if (endpoint.Content.Guid.Equals(guid))
|
||||
{
|
||||
endpoint.Header = endpoint.Content.Title;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,52 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Messages;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Models;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class OperationViewModel : ObservableObject
|
||||
public partial class OperationViewModel : ReactiveObject
|
||||
{
|
||||
public string Summary { get; }
|
||||
public string Description { get; }
|
||||
public string ReturnType { get; }
|
||||
public bool IsRequestBody { get; }
|
||||
public string? RequestBodyType { get; }
|
||||
public IAvaloniaReadOnlyList<PropertyClassViewModel> RequestClasses { get; }
|
||||
public IAvaloniaReadOnlyList<PropertyClassViewModel> ResponseClasses { get; }
|
||||
public IAvaloniaReadOnlyList<ParameterViewModel> PathParameters { get; }
|
||||
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
|
||||
public string? RequestTemplate { get; }
|
||||
|
||||
public OperationViewModel(OpenApiOperation operation)
|
||||
public OperationViewModel(OpenApiOperation operation, Models.Document document)
|
||||
{
|
||||
Summary = operation.Summary ?? string.Empty;
|
||||
Description = operation.Description ?? string.Empty;
|
||||
IsRequestBody = operation.RequestBody != null;
|
||||
ReturnType = GetReturnType(operation.Responses);
|
||||
RequestClasses = GetRequestClasses(operation.RequestBody);
|
||||
ResponseClasses = GetResponseClasses(operation.Responses);
|
||||
RequestClasses = GetRequestClasses(operation.RequestBody, document);
|
||||
ResponseClasses = GetResponseClasses(operation.Responses, document);
|
||||
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
|
||||
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
|
||||
RequestBodyType = GetRequestBodyType(operation.RequestBody);
|
||||
RequestTemplate = GetRequestTemplate(operation.RequestBody);
|
||||
RequestTemplate = GetRequestTemplate(operation.RequestBody, document);
|
||||
}
|
||||
|
||||
private string? GetRequestTemplate(OpenApiRequestBody? requestBody)
|
||||
public List<PropertyClassViewModel> RequestClasses { get; }
|
||||
|
||||
public List<PropertyClassViewModel> ResponseClasses { get; }
|
||||
|
||||
public List<ParameterViewModel> PathParameters { get; }
|
||||
|
||||
public List<ParameterViewModel> QueryParameters { get; }
|
||||
|
||||
public string? RequestTemplate { get; }
|
||||
|
||||
public string Summary { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public string ReturnType { get; }
|
||||
|
||||
public bool IsRequestBody { get; }
|
||||
|
||||
public string? RequestBodyType { get; }
|
||||
|
||||
private string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document)
|
||||
{
|
||||
var requestClasses = GetRequestClasses(requestBody);
|
||||
var requestClasses = GetRequestClasses(requestBody, document);
|
||||
if (requestClasses.Count == 0)
|
||||
{
|
||||
var type = GetRequestBodyType(requestBody);
|
||||
@@ -50,7 +58,7 @@ public partial class OperationViewModel : ObservableObject
|
||||
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
private List<string> CreateTemplate(AvaloniaList<PropertyClassViewModel> requestClasses)
|
||||
private List<string> CreateTemplate(List<PropertyClassViewModel> requestClasses)
|
||||
{
|
||||
if (requestClasses.Count == 0) return [];
|
||||
List<string> template = [];
|
||||
@@ -83,7 +91,7 @@ public partial class OperationViewModel : ObservableObject
|
||||
}
|
||||
else
|
||||
{
|
||||
AvaloniaList<PropertyClassViewModel> classes = [.. requestClasses];
|
||||
List<PropertyClassViewModel> classes = [.. requestClasses];
|
||||
classes.Remove(rootClass);
|
||||
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
||||
}
|
||||
@@ -121,9 +129,9 @@ public partial class OperationViewModel : ObservableObject
|
||||
return null;
|
||||
}
|
||||
|
||||
private AvaloniaList<ParameterViewModel> GetParameters(IList<OpenApiParameter> parameters, ParameterLocation location)
|
||||
private List<ParameterViewModel> GetParameters(IList<OpenApiParameter> parameters, ParameterLocation location)
|
||||
{
|
||||
var pathParameters = new AvaloniaList<ParameterViewModel>();
|
||||
var pathParameters = new List<ParameterViewModel>();
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
if (parameter.In != location) continue;
|
||||
@@ -133,21 +141,44 @@ public partial class OperationViewModel : ObservableObject
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses)
|
||||
private bool TryGetResponse(OpenApiResponses responses, [NotNullWhen(true)] out OpenApiResponse? response)
|
||||
{
|
||||
if (responses.TryGetValue("2XX", out var response)
|
||||
&& response.Content.TryGetValue("application/json", out var media))
|
||||
response = null;
|
||||
var flag = false;
|
||||
if (responses.TryGetValue("2XX", out var x))
|
||||
{
|
||||
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
||||
response = x;
|
||||
flag = true;
|
||||
}
|
||||
else if (responses.TryGetValue("200", out var y))
|
||||
{
|
||||
response = y;
|
||||
flag = true;
|
||||
}
|
||||
return flag;
|
||||
|
||||
}
|
||||
|
||||
private List<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document document)
|
||||
{
|
||||
if (!TryGetResponse(responses, out var response))
|
||||
return [];
|
||||
|
||||
if (response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var rawDocument = document.OpenApiDocument;
|
||||
var schema = media.Schema;
|
||||
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(schema, propertyClasses, document);
|
||||
if (schema == null) return [];
|
||||
|
||||
List<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(schema, propertyClasses, rawDocument);
|
||||
return propertyClasses;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private void WalkSchema(OpenApiSchema schema, AvaloniaList<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||
private void WalkSchema(OpenApiSchema schema, List<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||
{
|
||||
var type = GetSchemaType(schema);
|
||||
if (IsComponent(type))
|
||||
@@ -186,12 +217,12 @@ public partial class OperationViewModel : ObservableObject
|
||||
|| type.Contains("number"));
|
||||
}
|
||||
|
||||
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody)
|
||||
private List<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document document)
|
||||
{
|
||||
if (requestBody == null) return [];
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
||||
var rawDocument = document.OpenApiDocument;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
@@ -199,9 +230,9 @@ public partial class OperationViewModel : ObservableObject
|
||||
if (IsComponent(type))
|
||||
{
|
||||
var componentId = GetComponentId(schema);
|
||||
var componentSchema = document.Components.Schemas[componentId];
|
||||
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(componentSchema, propertyClasses, document);
|
||||
var componentSchema = rawDocument.Components.Schemas[componentId];
|
||||
List<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(componentSchema, propertyClasses, rawDocument);
|
||||
return propertyClasses;
|
||||
}
|
||||
}
|
||||
@@ -210,12 +241,15 @@ public partial class OperationViewModel : ObservableObject
|
||||
|
||||
private string GetReturnType(OpenApiResponses responses)
|
||||
{
|
||||
if (responses.TryGetValue("2XX", out var response)
|
||||
&& response.Content.TryGetValue("application/json", out var media))
|
||||
if (!TryGetResponse(responses, out var response))
|
||||
return "none";
|
||||
|
||||
if (response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -224,6 +258,7 @@ public partial class OperationViewModel : ObservableObject
|
||||
if (schema.Reference != null) return schema.Reference.Id;
|
||||
if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||
if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}";
|
||||
if (schema.Type == "array" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||
if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]";
|
||||
if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]";
|
||||
if (schema.Type == "array") return $"{schema.Items.Type}[]";
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class ParameterViewModel : ObservableObject
|
||||
public partial class ParameterViewModel : ReactiveObject
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Type { get; }
|
||||
public bool IsRequired { get; }
|
||||
[ObservableProperty] private string? _value = null;
|
||||
|
||||
public ParameterViewModel(string name, string type, bool isRequired, string? value = null)
|
||||
{
|
||||
Name = name;
|
||||
@@ -16,4 +12,13 @@ public partial class ParameterViewModel : ObservableObject
|
||||
IsRequired = isRequired;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public bool IsRequired { get; }
|
||||
|
||||
[Reactive]
|
||||
private string? _value;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,42 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.ViewModels.Shared;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class PathOperationViewModel : ObservableObject
|
||||
public partial class PathOperationViewModel : ReactiveObject
|
||||
{
|
||||
public string Path { get; }
|
||||
public OperationViewModel Operation { get; }
|
||||
|
||||
[ObservableProperty] private bool _isBusy;
|
||||
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest;
|
||||
|
||||
public PathOperationViewModel(PathOperation pathOperation)
|
||||
public PathOperationViewModel(PathOperation pathOperation, Document document, Tab tab)
|
||||
{
|
||||
Path = pathOperation.Path;
|
||||
Operation = new OperationViewModel(pathOperation.Operation);
|
||||
LcuRequest = new(() => new LcuRequestViewModel()
|
||||
Operation = new OperationViewModel(pathOperation.Operation, document);
|
||||
Request = new(() => new RequestViewModel(tab)
|
||||
{
|
||||
Method = pathOperation.Method.ToUpper()
|
||||
});
|
||||
Url = $"https://swagger.dysolix.dev/lcu/#/{Uri.EscapeDataString(pathOperation.Tag)}/{pathOperation.Operation.OperationId}";
|
||||
Markdown = $"[{pathOperation.Method.ToUpper()} {Path}]({Url})";
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public string Path { get; }
|
||||
|
||||
public OperationViewModel Operation { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
public string Markdown { get; }
|
||||
|
||||
[Reactive]
|
||||
private bool _isBusy;
|
||||
|
||||
[Reactive]
|
||||
private Lazy<RequestViewModel> _request;
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task SendRequest()
|
||||
{
|
||||
var sb = new StringBuilder(Path);
|
||||
@@ -46,7 +56,19 @@ public partial class PathOperationViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
LcuRequest.Value.RequestPath = sb.ToString();
|
||||
await LcuRequest.Value.ExecuteAsync();
|
||||
Request.Value.RequestPath = sb.ToString();
|
||||
await Request.Value.ExecuteAsync();
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private void CopyUrl()
|
||||
{
|
||||
App.MainWindow?.Clipboard?.SetTextAsync(Url);
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private void CopyMarkdown()
|
||||
{
|
||||
App.MainWindow?.Clipboard?.SetTextAsync(Markdown);
|
||||
}
|
||||
}
|
||||
|
||||
59
Needlework.Net/ViewModels/Pages/Endpoints/PluginViewModel.cs
Normal file
59
Needlework.Net/ViewModels/Pages/Endpoints/PluginViewModel.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using DynamicData;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class PluginViewModel : ReactiveObject
|
||||
{
|
||||
private readonly Subject<string> _pathOperationSelectedSubject = new();
|
||||
|
||||
public PluginViewModel(string endpoint, Models.Document document, Tab tab)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
PathOperations = [.. document.Plugins[endpoint].Select(x => new PathOperationViewModel(x, document, tab))];
|
||||
FilteredPathOperations = new ObservableCollection<PathOperationViewModel>(PathOperations);
|
||||
|
||||
this.WhenAnyValue(x => x.Search)
|
||||
.Subscribe(search =>
|
||||
|
||||
{
|
||||
FilteredPathOperations.Clear();
|
||||
if (string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
FilteredPathOperations.AddRange(PathOperations);
|
||||
return;
|
||||
}
|
||||
FilteredPathOperations.AddRange(PathOperations.Where(o => o.Path.Contains(search, StringComparison.InvariantCultureIgnoreCase)));
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.SelectedPathOperation)
|
||||
.Subscribe(pathOperation =>
|
||||
{
|
||||
if (pathOperation == null) return;
|
||||
_pathOperationSelectedSubject.OnNext(pathOperation.Operation.RequestTemplate ?? string.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
public IObservable<string> PathOperationSelected { get { return _pathOperationSelectedSubject; } }
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
public string Title => Endpoint;
|
||||
|
||||
public ObservableCollection<PathOperationViewModel> PathOperations { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
private ObservableCollection<PathOperationViewModel> _filteredPathOperations = [];
|
||||
|
||||
[Reactive]
|
||||
private PathOperationViewModel? _selectedPathOperation;
|
||||
|
||||
[Reactive]
|
||||
private string? _search;
|
||||
}
|
||||
@@ -1,22 +1,18 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public class PropertyClassViewModel : ObservableObject
|
||||
public class PropertyClassViewModel : ReactiveObject
|
||||
{
|
||||
public string Id { get; }
|
||||
public IAvaloniaReadOnlyList<PropertyFieldViewModel> PropertyFields { get; } = new AvaloniaList<PropertyFieldViewModel>();
|
||||
public IAvaloniaReadOnlyList<PropertyEnumViewModel> PropertyEnums { get; } = new AvaloniaList<PropertyEnumViewModel>();
|
||||
|
||||
public PropertyClassViewModel(string id, IDictionary<string, OpenApiSchema> properties, IList<IOpenApiAny> enumValue)
|
||||
{
|
||||
AvaloniaList<PropertyFieldViewModel> propertyFields = [];
|
||||
AvaloniaList<PropertyEnumViewModel> propertyEnums = [];
|
||||
List<PropertyFieldViewModel> propertyFields = [];
|
||||
List<PropertyEnumViewModel> propertyEnums = [];
|
||||
foreach ((var propertyName, var propertySchema) in properties)
|
||||
{
|
||||
var type = OperationViewModel.GetSchemaType(propertySchema);
|
||||
@@ -28,8 +24,14 @@ public class PropertyClassViewModel : ObservableObject
|
||||
var propertyEnum = new PropertyEnumViewModel(enumValue);
|
||||
propertyEnums.Add(propertyEnum);
|
||||
}
|
||||
PropertyFields = propertyFields;
|
||||
PropertyEnums = propertyEnums;
|
||||
PropertyFields = [.. propertyFields];
|
||||
PropertyEnums = [.. propertyEnums];
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public ObservableCollection<PropertyFieldViewModel> PropertyFields { get; } = [];
|
||||
|
||||
public ObservableCollection<PropertyEnumViewModel> PropertyEnums { get; } = [];
|
||||
}
|
||||
@@ -6,11 +6,13 @@ namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public class PropertyEnumViewModel
|
||||
{
|
||||
public string Type { get; } = "Enum";
|
||||
public string Values { get; }
|
||||
|
||||
public PropertyEnumViewModel(IList<IOpenApiAny> enumValue)
|
||||
{
|
||||
Values = $"[{string.Join(", ", enumValue.Select(x => $"\"{((OpenApiString)x).Value}\"").ToList())}]";
|
||||
}
|
||||
|
||||
public string Type { get; } = "Enum";
|
||||
|
||||
public string Values { get; }
|
||||
|
||||
}
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
public class PropertyFieldViewModel
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Type { get; }
|
||||
|
||||
public PropertyFieldViewModel(string name, string type)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
}
|
||||
@@ -1,17 +1,11 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using BlossomiShymae.Briar.Utils;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class ResponseViewModel : ObservableObject
|
||||
public partial class ResponseViewModel : ReactiveObject
|
||||
{
|
||||
[ObservableProperty] private string? _path;
|
||||
[ObservableProperty] private string? _status;
|
||||
[ObservableProperty] private string? _authentication;
|
||||
[ObservableProperty] private string? _username;
|
||||
[ObservableProperty] private string? _password;
|
||||
[ObservableProperty] private string? _authorization;
|
||||
|
||||
public ResponseViewModel(string path)
|
||||
{
|
||||
Path = path;
|
||||
@@ -26,6 +20,24 @@ public partial class ResponseViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[Reactive]
|
||||
private string? _path;
|
||||
|
||||
[Reactive]
|
||||
private string? _status;
|
||||
|
||||
[Reactive]
|
||||
private string? _authentication;
|
||||
|
||||
[Reactive]
|
||||
private string? _username;
|
||||
|
||||
[Reactive]
|
||||
private string? _password;
|
||||
|
||||
[Reactive]
|
||||
private string? _authorization;
|
||||
|
||||
private static ProcessInfo? GetProcessInfo()
|
||||
{
|
||||
if (ProcessFinder.IsActive()) return ProcessFinder.GetProcessInfo();
|
||||
|
||||
26
Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs
Normal file
26
Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Avalonia.Platform;
|
||||
using Needlework.Net.Models;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Home;
|
||||
|
||||
public partial class HomeViewModel : PageBase
|
||||
{
|
||||
public HomeViewModel(IScreen? screen = null) : base("Home", "home", int.MinValue)
|
||||
{
|
||||
Libraries = JsonSerializer.Deserialize<List<Library>>(AssetLoader.Open(new Uri($"avares://NeedleworkDotNet/Assets/libraries.json")))!
|
||||
.Select(library => new LibraryViewModel(library))
|
||||
.ToList();
|
||||
HostScreen = screen ?? Locator.Current.GetService<IScreen>()!;
|
||||
}
|
||||
public List<LibraryViewModel> Libraries { get; }
|
||||
|
||||
public override string? UrlPathSegment => "home";
|
||||
|
||||
public override IScreen HostScreen { get; }
|
||||
}
|
||||
15
Needlework.Net/ViewModels/Pages/Home/LibraryViewModel.cs
Normal file
15
Needlework.Net/ViewModels/Pages/Home/LibraryViewModel.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Needlework.Net.Models;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Home
|
||||
{
|
||||
public class LibraryViewModel : ReactiveObject
|
||||
{
|
||||
public LibraryViewModel(Library library)
|
||||
{
|
||||
Library = library;
|
||||
}
|
||||
|
||||
public Library Library { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages;
|
||||
|
||||
public partial class HomeViewModel : PageBase
|
||||
{
|
||||
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages;
|
||||
|
||||
|
||||
public abstract partial class PageBase(string displayName, string icon, int index = 0) : ObservableValidator
|
||||
public abstract partial class PageBase(string displayName, string icon, int index = 0) : ReactiveObject, IRoutableViewModel
|
||||
{
|
||||
[ObservableProperty] private string _displayName = displayName;
|
||||
[ObservableProperty] private string _icon = icon;
|
||||
[ObservableProperty] private int _index = index;
|
||||
public string DisplayName { get; } = displayName;
|
||||
|
||||
public string Icon { get; } = icon;
|
||||
|
||||
public int Index { get; } = index;
|
||||
|
||||
public abstract string? UrlPathSegment { get; }
|
||||
|
||||
public abstract IScreen HostScreen { get; }
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using BlossomiShymae.Briar.WebSocket.Events;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Websocket;
|
||||
namespace Needlework.Net.ViewModels.Pages.WebSocket;
|
||||
|
||||
public class EventViewModel : ObservableObject
|
||||
public class EventViewModel : ReactiveObject
|
||||
{
|
||||
public string Time { get; }
|
||||
public string Type { get; }
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +1,150 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using BlossomiShymae.Briar;
|
||||
using BlossomiShymae.Briar.WebSocket.Events;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using Splat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Websocket.Client;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Websocket;
|
||||
namespace Needlework.Net.ViewModels.Pages.WebSocket;
|
||||
|
||||
public partial class WebsocketViewModel : PageBase
|
||||
public partial class WebSocketViewModel : PageBase, IEnableLogger
|
||||
{
|
||||
public ObservableCollection<EventViewModel> EventLog { get; } = [];
|
||||
public SemaphoreSlim EventLogLock { get; } = new(1, 1);
|
||||
|
||||
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
||||
[ObservableProperty] private string _search = string.Empty;
|
||||
[ObservableProperty] private bool _isAttach = true;
|
||||
[ObservableProperty] private bool _isTail = false;
|
||||
[ObservableProperty] private EventViewModel? _selectedEventLog = null;
|
||||
|
||||
private Dictionary<string, EventMessage> _events = [];
|
||||
|
||||
private readonly object _tokenLock = new();
|
||||
|
||||
private readonly IFlurlClient _githubUserContentClient;
|
||||
|
||||
// public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
||||
|
||||
|
||||
public WebSocketViewModel(IScreen? screen = null, IFlurlClientCache? clients = null) : base("Event Viewer", "plug", -100)
|
||||
{
|
||||
_githubUserContentClient = clients?.Get("GithubUserContentClient") ?? Locator.Current.GetService<IFlurlClientCache>()?.Get("GithubUserContentClient")!;
|
||||
|
||||
HostScreen = screen ?? Locator.Current.GetService<IScreen>()!;
|
||||
|
||||
//EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
||||
//Task.Run(async () =>
|
||||
//{
|
||||
// await InitializeEventTypes();
|
||||
// InitializeWebsocket();
|
||||
//});
|
||||
}
|
||||
|
||||
public override string? UrlPathSegment => "websocket";
|
||||
|
||||
public override ReactiveUI.IScreen HostScreen { get; }
|
||||
|
||||
|
||||
public CancellationTokenSource TokenSource { get; set; } = new();
|
||||
|
||||
public WebsocketClient? Client { get; set; }
|
||||
|
||||
public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
||||
public List<IDisposable> ClientDisposables = [];
|
||||
|
||||
public WebsocketViewModel() : base("Event Viewer", "plug", -100)
|
||||
public SemaphoreSlim EventLogLock { get; } = new(1, 1);
|
||||
|
||||
[Reactive]
|
||||
public ObservableCollection<EventViewModel> EventLog { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
private string _search = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
private bool _isAttach = true;
|
||||
|
||||
[Reactive]
|
||||
private bool _isTail;
|
||||
|
||||
[Reactive]
|
||||
private EventViewModel? _selectedEventLog;
|
||||
|
||||
[Reactive]
|
||||
private ObservableCollection<string> _eventTypes = [];
|
||||
|
||||
[Reactive]
|
||||
private string _eventType = "OnJsonApiEvent";
|
||||
|
||||
[ObservableAsProperty]
|
||||
private ObservableCollection<EventViewModel> _filteredEventLog = [];
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task<List<string>> GetEventTypesAsync()
|
||||
{
|
||||
EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
||||
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
||||
thread.Start();
|
||||
var file = await _githubUserContentClient.Request("/dysolix/hasagi-types/refs/heads/main/dist/lcu-events.d.ts")
|
||||
.GetStringAsync();
|
||||
var matches = EventTypesRegex().Matches(file);
|
||||
var eventTypes = matches.Select(m => m.Groups[1].Value)
|
||||
.ToList();
|
||||
return eventTypes;
|
||||
}
|
||||
|
||||
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;
|
||||
this.Log()
|
||||
.Debug("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();
|
||||
this.Log()
|
||||
.Debug("Initialized new connection: {EventType}", EventType);
|
||||
TokenSource = tokenSource;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedEventLogChanged(EventViewModel? value)
|
||||
{
|
||||
if (value == null) return;
|
||||
if (_events.TryGetValue(value.Key, out var message))
|
||||
{
|
||||
var text = JsonSerializer.Serialize(message, App.JsonSerializerOptions);
|
||||
if (text.Length >= App.MaxCharacters) WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(text));
|
||||
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(text), nameof(WebsocketViewModel));
|
||||
}
|
||||
}
|
||||
//partial void OnSelectedEventLogChanged(EventViewModel? value)
|
||||
//{
|
||||
// if (value == null) return;
|
||||
// if (_events.TryGetValue(value.Key, out var message))
|
||||
// {
|
||||
// var text = JsonSerializer.Serialize(message, App.JsonSerializerOptions);
|
||||
// if (text.Length >= App.MaxCharacters) WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(text));
|
||||
// else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(text), nameof(WebSocketViewModel));
|
||||
// }
|
||||
//}
|
||||
|
||||
[RelayCommand]
|
||||
[ReactiveCommand]
|
||||
private void Clear()
|
||||
{
|
||||
_events.Clear();
|
||||
@@ -79,17 +153,22 @@ public partial class WebsocketViewModel : PageBase
|
||||
|
||||
private void OnReconnection(ReconnectionInfo info)
|
||||
{
|
||||
Trace.WriteLine($"-- Reconnection --\nType{info.Type}");
|
||||
this.Log()
|
||||
.Debug("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();
|
||||
this.Log()
|
||||
.Debug("Disconnected: {Type}", info.Type);
|
||||
InitializeWebsocket();
|
||||
}
|
||||
|
||||
//partial void OnEventTypeChanged(string value)
|
||||
//{
|
||||
// InitializeWebsocket();
|
||||
//}
|
||||
|
||||
private void OnMessage(EventMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () =>
|
||||
@@ -122,4 +201,7 @@ public partial class WebsocketViewModel : PageBase
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[GeneratedRegex("\"(.*?)\":")]
|
||||
public static partial Regex EventTypesRegex();
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
using Avalonia.Media;
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.ViewModels.MainWindow;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Shared;
|
||||
|
||||
public partial class LcuRequestViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string? _method = "GET";
|
||||
[ObservableProperty] private SolidColorBrush _color = new(GetColor("GET"));
|
||||
|
||||
[ObservableProperty] private bool _isRequestBusy = false;
|
||||
[ObservableProperty] private string? _requestPath = null;
|
||||
[ObservableProperty] private string? _requestBody = null;
|
||||
|
||||
[ObservableProperty] private string? _responsePath = null;
|
||||
[ObservableProperty] private string? _responseStatus = null;
|
||||
[ObservableProperty] private string? _responseAuthentication = null;
|
||||
[ObservableProperty] private string? _responseUsername = null;
|
||||
[ObservableProperty] private string? _responsePassword = null;
|
||||
[ObservableProperty] private string? _responseAuthorization = null;
|
||||
[ObservableProperty] private string? _responseBody = null;
|
||||
|
||||
public event EventHandler<LcuRequestViewModel>? RequestText;
|
||||
public event EventHandler<string>? UpdateText;
|
||||
|
||||
partial void OnMethodChanged(string? oldValue, string? newValue)
|
||||
{
|
||||
if (newValue == null) return;
|
||||
|
||||
Color = new(GetColor(newValue));
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsRequestBusy = true;
|
||||
if (string.IsNullOrEmpty(RequestPath))
|
||||
throw new Exception("Path is empty.");
|
||||
|
||||
var method = Method switch
|
||||
{
|
||||
"GET" => HttpMethod.Get,
|
||||
"POST" => HttpMethod.Post,
|
||||
"PUT" => HttpMethod.Put,
|
||||
"DELETE" => HttpMethod.Delete,
|
||||
"HEAD" => HttpMethod.Head,
|
||||
"PATCH" => HttpMethod.Patch,
|
||||
"OPTIONS" => HttpMethod.Options,
|
||||
"TRACE" => HttpMethod.Trace,
|
||||
_ => throw new Exception("Method is not selected or missing."),
|
||||
};
|
||||
|
||||
var processInfo = ProcessFinder.GetProcessInfo();
|
||||
RequestText?.Invoke(this, this);
|
||||
var content = new StringContent(RequestBody ?? string.Empty, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
var client = Connector.GetLcuHttpClientInstance();
|
||||
var response = await client.SendAsync(new(method, RequestPath) { Content = content });
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
var responseBody = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
var body = responseBody.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
|
||||
if (body.Length >= App.MaxCharacters)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body));
|
||||
UpdateText?.Invoke(this, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseBody = body;
|
||||
UpdateText?.Invoke(this, body);
|
||||
}
|
||||
|
||||
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
|
||||
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
|
||||
ResponseAuthentication = riotAuthentication.Value;
|
||||
ResponseAuthorization = $"Basic {riotAuthentication.Value}";
|
||||
ResponseUsername = riotAuthentication.Username;
|
||||
ResponsePassword = riotAuthentication.Password;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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);
|
||||
|
||||
ResponseStatus = null;
|
||||
ResponsePath = null;
|
||||
ResponseAuthentication = null;
|
||||
ResponseAuthorization = null;
|
||||
ResponseUsername = null;
|
||||
ResponsePassword = null;
|
||||
ResponseBody = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRequestBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetColor(string method) => method switch
|
||||
{
|
||||
"GET" => Avalonia.Media.Color.FromRgb(95, 99, 186),
|
||||
"POST" => Avalonia.Media.Color.FromRgb(103, 186, 95),
|
||||
"PUT" => Avalonia.Media.Color.FromRgb(186, 139, 95),
|
||||
"DELETE" => Avalonia.Media.Color.FromRgb(186, 95, 95),
|
||||
"HEAD" => Avalonia.Media.Color.FromRgb(136, 95, 186),
|
||||
"PATCH" => Avalonia.Media.Color.FromRgb(95, 186, 139),
|
||||
_ => throw new InvalidOperationException("Method does not have assigned color.")
|
||||
};
|
||||
}
|
||||
204
Needlework.Net/ViewModels/Shared/RequestViewModel.cs
Normal file
204
Needlework.Net/ViewModels/Shared/RequestViewModel.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
using Avalonia.Media;
|
||||
using AvaloniaEdit.Document;
|
||||
using BlossomiShymae.Briar;
|
||||
using BlossomiShymae.Briar.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using Splat;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Shared;
|
||||
|
||||
public partial class RequestViewModel : ReactiveObject, IEnableLogger
|
||||
{
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
private readonly Tab _tab;
|
||||
|
||||
public RequestViewModel(Pages.Endpoints.Tab tab, NotificationService? notificationService = null)
|
||||
{
|
||||
_tab = tab;
|
||||
_notificationService = notificationService ?? Locator.Current.GetService<NotificationService>()!;
|
||||
|
||||
_colorHelper = this.WhenAnyValue(x => x.Method)
|
||||
.Select(method => GetSolidCrushBrush(method ?? "GET"))
|
||||
.ToProperty(this, x => x.Color);
|
||||
}
|
||||
|
||||
[ObservableAsProperty]
|
||||
private SolidColorBrush _color = GetSolidCrushBrush("GET");
|
||||
|
||||
[Reactive]
|
||||
private string? _method = "GET";
|
||||
|
||||
[Reactive]
|
||||
private bool _isRequestBusy;
|
||||
|
||||
[Reactive]
|
||||
private string? _requestPath;
|
||||
|
||||
[Reactive]
|
||||
private TextDocument _requestDocument = new();
|
||||
|
||||
[Reactive]
|
||||
private string? _responsePath;
|
||||
|
||||
[Reactive]
|
||||
private string? _responseStatus;
|
||||
|
||||
[Reactive]
|
||||
private string? _responseAuthentication;
|
||||
|
||||
[Reactive]
|
||||
private string? _responseUsername;
|
||||
|
||||
[Reactive]
|
||||
private string? _responsePassword;
|
||||
|
||||
[Reactive]
|
||||
private string? _responseAuthorization;
|
||||
|
||||
[Reactive]
|
||||
private TextDocument _responseDocument = new();
|
||||
|
||||
public async Task ExecuteAsync()
|
||||
{
|
||||
switch (_tab)
|
||||
{
|
||||
case Tab.LCU:
|
||||
await ExecuteLcuAsync();
|
||||
break;
|
||||
case Tab.GameClient:
|
||||
await ExecuteGameClientAsync();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteGameClientAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsRequestBusy = true;
|
||||
if (string.IsNullOrEmpty(RequestPath))
|
||||
throw new Exception("Path is empty.");
|
||||
var method = GetMethod();
|
||||
|
||||
this.Log()
|
||||
.Debug("Sending request: {Tuple}", (Method, RequestPath));
|
||||
|
||||
var content = new StringContent(RequestDocument.Text, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
var client = Connector.GetGameHttpClientInstance();
|
||||
var response = await client.SendAsync(new HttpRequestMessage(method, RequestPath) { Content = content });
|
||||
var responseBody = await response.Content.ReadAsByteArrayAsync();
|
||||
var body = responseBody.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
|
||||
|
||||
ResponseDocument = new(body);
|
||||
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
|
||||
ResponsePath = $"https://127.0.0.1:2999{RequestPath}";
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Log()
|
||||
.Error(ex, "Request failed: {Tuple}", (Method, RequestPath));
|
||||
_notificationService.Notify("Request Failed", ex.Message, InfoBarSeverity.Error);
|
||||
|
||||
ResponseStatus = null;
|
||||
ResponsePath = null;
|
||||
ResponseAuthentication = null;
|
||||
ResponseAuthorization = null;
|
||||
ResponseUsername = null;
|
||||
ResponsePassword = null;
|
||||
ResponseDocument = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRequestBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteLcuAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsRequestBusy = true;
|
||||
if (string.IsNullOrEmpty(RequestPath))
|
||||
throw new Exception("Path is empty.");
|
||||
var method = GetMethod();
|
||||
|
||||
this.Log()
|
||||
.Debug("Sending request: {Tuple}", (Method, RequestPath));
|
||||
|
||||
var processInfo = ProcessFinder.GetProcessInfo();
|
||||
var content = new StringContent(RequestDocument.Text, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
var client = Connector.GetLcuHttpClientInstance();
|
||||
var response = await client.SendAsync(new(method, RequestPath) { Content = content });
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
var responseBody = await response.Content.ReadAsByteArrayAsync();
|
||||
var body = responseBody.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
|
||||
|
||||
ResponseDocument = new(body);
|
||||
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
|
||||
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
|
||||
ResponseAuthentication = riotAuthentication.Value;
|
||||
ResponseAuthorization = $"Basic {riotAuthentication.Value}";
|
||||
ResponseUsername = riotAuthentication.Username;
|
||||
ResponsePassword = riotAuthentication.Password;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Log()
|
||||
.Error(ex, "Request failed: {Tuple}", (Method, RequestPath));
|
||||
_notificationService.Notify("Request Failed", ex.Message, InfoBarSeverity.Error);
|
||||
|
||||
ResponseStatus = null;
|
||||
ResponsePath = null;
|
||||
ResponseAuthentication = null;
|
||||
ResponseAuthorization = null;
|
||||
ResponseUsername = null;
|
||||
ResponsePassword = null;
|
||||
ResponseDocument = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRequestBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private HttpMethod GetMethod()
|
||||
{
|
||||
return Method switch
|
||||
{
|
||||
"GET" => HttpMethod.Get,
|
||||
"POST" => HttpMethod.Post,
|
||||
"PUT" => HttpMethod.Put,
|
||||
"DELETE" => HttpMethod.Delete,
|
||||
"HEAD" => HttpMethod.Head,
|
||||
"PATCH" => HttpMethod.Patch,
|
||||
"OPTIONS" => HttpMethod.Options,
|
||||
"TRACE" => HttpMethod.Trace,
|
||||
_ => throw new Exception("Method is not selected or missing."),
|
||||
};
|
||||
}
|
||||
|
||||
private static SolidColorBrush GetSolidCrushBrush(string? method = null) => new(method switch
|
||||
{
|
||||
"GET" or null => Avalonia.Media.Color.FromRgb(95, 99, 186),
|
||||
"POST" => Avalonia.Media.Color.FromRgb(103, 186, 95),
|
||||
"PUT" => Avalonia.Media.Color.FromRgb(186, 139, 95),
|
||||
"DELETE" => Avalonia.Media.Color.FromRgb(186, 95, 95),
|
||||
"HEAD" => Avalonia.Media.Color.FromRgb(136, 95, 186),
|
||||
"PATCH" => Avalonia.Media.Color.FromRgb(95, 186, 139),
|
||||
_ => throw new InvalidOperationException("Method does not have assigned color.")
|
||||
});
|
||||
}
|
||||
@@ -6,10 +6,11 @@
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.MainWindow"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.MainWindow.MainWindowView"
|
||||
x:Class="Needlework.Net.Views.MainWindow.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Title="Needlework.Net"
|
||||
Icon="/Assets/app.ico"
|
||||
@@ -34,14 +35,13 @@
|
||||
Needlework.Net
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<ui:NavigationView AlwaysShowHeader="False"
|
||||
<ui:NavigationView Name="NavigationView"
|
||||
AlwaysShowHeader="False"
|
||||
PaneDisplayMode="Left"
|
||||
IsSettingsVisible="False"
|
||||
IsPaneOpen="False"
|
||||
OpenPaneLength="200"
|
||||
Grid.Row="1"
|
||||
MenuItemsSource="{Binding MenuItems}"
|
||||
SelectedItem="{Binding SelectedMenuItem}">
|
||||
Grid.Row="1">
|
||||
<ui:NavigationView.PaneFooter>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel.Styles>
|
||||
@@ -53,20 +53,16 @@
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
<Button
|
||||
<Button Name="GithubButton"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenUrlCommand}"
|
||||
CommandParameter="https://github.com/BlossomiShymae/Needlework.Net"
|
||||
ToolTip.Tip="Open on GitHub."
|
||||
Margin="4">
|
||||
<materialIcons:MaterialIcon Kind="Github" />
|
||||
</Button>
|
||||
<Button
|
||||
<Button Name="DiscordButton"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenUrlCommand}"
|
||||
CommandParameter="https://discord.gg/chEvEX5J4E"
|
||||
ToolTip.Tip="Open Discord server."
|
||||
Margin="4">
|
||||
<i:Icon Value="fa-brand fa-discord" />
|
||||
@@ -74,27 +70,14 @@
|
||||
</StackPanel>
|
||||
</ui:NavigationView.PaneFooter>
|
||||
<Grid>
|
||||
<TransitioningContentControl Content="{Binding CurrentPage}"/>
|
||||
<Button Content="{Binding Version}"
|
||||
<reactiveUi:RoutedViewHost Name="RoutedViewHost"/>
|
||||
<Button Name="VersionButton"
|
||||
Background="RoyalBlue"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="16"/>
|
||||
<ItemsRepeater ItemsSource="{Binding InfoBarItems}"
|
||||
VerticalAlignment="Bottom">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Margin="4">
|
||||
<ui:InfoBar
|
||||
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
|
||||
Title="{Binding Title}"
|
||||
IsOpen="{Binding IsOpen}"
|
||||
Severity="{Binding Severity}"
|
||||
Message="{Binding Message}"
|
||||
ActionButton="{Binding ActionButton}"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
<ItemsControl Name="NotificationItemsControl"
|
||||
VerticalAlignment="Bottom"/>
|
||||
</Grid>
|
||||
</ui:NavigationView>
|
||||
</Grid>
|
||||
70
Needlework.Net/Views/MainWindow/MainWindow.axaml.cs
Normal file
70
Needlework.Net/Views/MainWindow/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
using Needlework.Net.ViewModels.MainWindow;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Needlework.Net.Views.MainWindow;
|
||||
|
||||
public partial class MainWindow : AppWindow, IViewFor<MainWindowViewModel>
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
TransparencyLevelHint = [WindowTransparencyLevel.Mica, WindowTransparencyLevel.None];
|
||||
Background = IsWindows11 ? null : Background;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.PageItems, v => v.NavigationView.MenuItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedPageItem, v => v.NavigationView.SelectedItem)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Router, v => v.RoutedViewHost.Router)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Notifications, v => v.NotificationItemsControl.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Version, v => v.VersionButton.Content)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<MainWindowViewModel?> ViewModelProperty = AvaloniaProperty
|
||||
.Register<MainWindow, MainWindowViewModel?>(nameof(ViewModel));
|
||||
|
||||
public MainWindowViewModel? ViewModel
|
||||
{
|
||||
get => GetValue(ViewModelProperty);
|
||||
set => SetValue(ViewModelProperty, value);
|
||||
}
|
||||
|
||||
object? IViewFor.ViewModel
|
||||
{
|
||||
get => ViewModel;
|
||||
set => ViewModel = (MainWindowViewModel?)value;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == DataContextProperty)
|
||||
{
|
||||
if (ReferenceEquals(change.OldValue, ViewModel)
|
||||
&& change.NewValue is null or MainWindowViewModel)
|
||||
{
|
||||
SetCurrentValue(ViewModelProperty, change.NewValue);
|
||||
}
|
||||
}
|
||||
else if (change.Property == ViewModelProperty)
|
||||
{
|
||||
if (ReferenceEquals(change.OldValue, DataContext))
|
||||
{
|
||||
SetCurrentValue(DataContextProperty, change.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
|
||||
namespace Needlework.Net.Views.MainWindow;
|
||||
|
||||
public partial class MainWindowView : AppWindow
|
||||
{
|
||||
public MainWindowView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
}
|
||||
}
|
||||
20
Needlework.Net/Views/MainWindow/NotificationView.axaml
Normal file
20
Needlework.Net/Views/MainWindow/NotificationView.axaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.MainWindow"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.MainWindow.NotificationView"
|
||||
x:DataType="vm:NotificationViewModel">
|
||||
<Border Margin="4">
|
||||
<ui:InfoBar Name="InfoBar"
|
||||
IsOpen="True">
|
||||
<ui:InfoBar.ActionButton>
|
||||
<Button Name="InfoBarButton"/>
|
||||
</ui:InfoBar.ActionButton>
|
||||
</ui:InfoBar>
|
||||
</Border>
|
||||
</UserControl>
|
||||
28
Needlework.Net/Views/MainWindow/NotificationView.axaml.cs
Normal file
28
Needlework.Net/Views/MainWindow/NotificationView.axaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.MainWindow;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Needlework.Net.Views.MainWindow;
|
||||
|
||||
public partial class NotificationView : ReactiveUserControl<NotificationViewModel>
|
||||
{
|
||||
public NotificationView()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Notification.Title, v => v.InfoBar.Title)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Notification.Message, v => v.InfoBar.Message)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Notification.InfoBarSeverity, v => v.InfoBar.Severity)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.IsButtonVisible, v => v.InfoBarButton.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.OpenUrlCommand, v => v.InfoBarButton);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
54
Needlework.Net/Views/Pages/About/AboutPage.axaml
Normal file
54
Needlework.Net/Views/Pages/About/AboutPage.axaml
Normal file
@@ -0,0 +1,54 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.About"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.About.AboutPage"
|
||||
x:DataType="vm:AboutViewModel">
|
||||
<ScrollViewer Margin="8">
|
||||
<WrapPanel HorizontalAlignment="Center">
|
||||
<WrapPanel.Styles>
|
||||
<Style Selector="controls|UserCard">
|
||||
<Setter Property="Width" Value="272"/>
|
||||
<Setter Property="MaxHeight" Value="378"/>
|
||||
<Setter Property="Margin" Value="0 16 32 16"/>
|
||||
</Style>
|
||||
</WrapPanel.Styles>
|
||||
<controls:UserCard UserImage="/Assets/Users/blossomishymae.png"
|
||||
UserName="estrogen elf"
|
||||
UserGithub="BlossomiShymae">
|
||||
Needlework.Net is the .NET rewrite of Needlework. This tool was made to help others with LCU and Game Client development. Feel free to ask any questions
|
||||
or help contribute to the project! Made with love. 💜
|
||||
</controls:UserCard>
|
||||
<controls:UserCard UserImage="/Assets/Users/dysolix.png"
|
||||
UserName="dysolix"
|
||||
UserGithub="dysolix">
|
||||
For providing LCU Schema, the auto-generated OpenAPI document for the LCU.
|
||||
</controls:UserCard>
|
||||
<controls:UserCard UserImage="/Assets/Users/sylv.jpg"
|
||||
UserName="Sylv"
|
||||
UserGithub="AlsoSylv">
|
||||
For providing a fixed up-to-date Game Client schema.
|
||||
</controls:UserCard>
|
||||
<controls:UserCard UserImage="/Assets/Users/ray.png"
|
||||
UserName="Ray"
|
||||
UserGithub="Hi-Ray">
|
||||
For guidance, advice, and providing help via HextechDocs.
|
||||
</controls:UserCard>
|
||||
<controls:UserCard UserImage="/Assets/Users/dubble.png"
|
||||
UserName="dubble"
|
||||
UserGithub="cuppachino">
|
||||
For encouraging me to publish Needlework.Net and other ideas.
|
||||
</controls:UserCard>
|
||||
<controls:UserCard UserImage="/Assets/Users/aoshiw.png"
|
||||
UserName="AoshiW"
|
||||
UserGithub="AoshiW">
|
||||
For PR.
|
||||
</controls:UserCard>
|
||||
</WrapPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
15
Needlework.Net/Views/Pages/About/AboutPage.axaml.cs
Normal file
15
Needlework.Net/Views/Pages/About/AboutPage.axaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.About;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.About;
|
||||
|
||||
public partial class AboutPage : ReactiveUserControl<AboutViewModel>
|
||||
{
|
||||
public AboutPage()
|
||||
{
|
||||
this.WhenActivated(disposables => { });
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.AboutView"
|
||||
x:DataType="vm:AboutViewModel">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Theme" Value="{StaticResource TransparentButton}"/>
|
||||
<Setter Property="Command" Value="{Binding OpenUrlCommand}"/>
|
||||
</Style>
|
||||
<Style Selector="i|Icon">
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Grid Margin="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Spacing="8">
|
||||
<Grid HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:Card Margin="8">
|
||||
<Image Source="/Assets/Users/blossomishymae.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="200"
|
||||
Height="200"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="8 0 0 0">
|
||||
<controls:Card Width="400" Margin="8">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
||||
Margin="0 0 8 0">Blossomi Shymae</TextBlock>
|
||||
<Button CommandParameter="https://github.com/BlossomiShymae">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="400" Margin="8">
|
||||
<StackPanel >
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">About</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Needlework.Net is the .NET rewrite of Needlework. This tool was made to help others with LCU development. Feel free to ask any questions
|
||||
or help contribute to the project! Made with love. 💜
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Width="800">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Thanks to the friends and people who made this tool possible...</TextBlock>
|
||||
</Border>
|
||||
<WrapPanel Orientation="Horizontal">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8">
|
||||
<controls:Card>
|
||||
<Image Source="/Assets/Users/dysolix.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="100"
|
||||
Height="100"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="2 0 0 0">
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Margin="0 0 8 0">dysolix</TextBlock>
|
||||
<Button CommandParameter="https://github.com/dysolix">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel >
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
For providing and hosting an auto-generated OpenAPI document of the LCU.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8">
|
||||
<controls:Card>
|
||||
<Image Source="/Assets/Users/ray.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="100"
|
||||
Height="100"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="2 0 0 0">
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Margin="0 0 8 0">Ray</TextBlock>
|
||||
<Button CommandParameter="https://github.com/Hi-Ray">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel >
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
For guidance, advice, or providing help via HextechDocs.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8">
|
||||
<controls:Card>
|
||||
<Image Source="/Assets/Users/dubble.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="100"
|
||||
Height="100"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="4 0 0 0">
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Margin="0 0 8 0">dubble</TextBlock>
|
||||
<Button CommandParameter="https://github.com/cuppachino">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel >
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
For encouraging me to publish Needlework. This project may never have seen the light of day without him.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8">
|
||||
<controls:Card>
|
||||
<Image Source="/Assets/Users/community.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="100"
|
||||
Height="100"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="4 0 0 0">
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Width="250"
|
||||
TextWrapping="Wrap">Third Party Developer Community</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel >
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
For providing numerous documentation on the LCU.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Views.Pages;
|
||||
|
||||
public partial class AboutView : UserControl
|
||||
{
|
||||
public AboutView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -3,40 +3,47 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Console"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.ConsoleView"
|
||||
x:Class="Needlework.Net.Views.Pages.Console.ConsolePage"
|
||||
x:DataType="vm:ConsoleViewModel">
|
||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||
<controls:BusyArea Name="BusyArea"
|
||||
BusyText="Loading...">
|
||||
<Grid Margin="16" RowDefinitions="auto,*" ColumnDefinitions="*,*">
|
||||
<Grid Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2">
|
||||
<StackPanel Margin="0 0 0 16">
|
||||
<StackPanel Margin="0 0 0 8">
|
||||
<Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto">
|
||||
<ComboBox ItemsSource="{Binding RequestMethods}"
|
||||
SelectedItem="{Binding LcuRequest.Method}"
|
||||
<!-- Worst behavior of Avalonia with ReactiveUI, but works for now.
|
||||
https://stackoverflow.com/a/78409519
|
||||
https://github.com/AvaloniaUI/Avalonia/discussions/17736#discussioncomment-11525997 -->
|
||||
<ComboBox Name="RequestMethodsComboBox"
|
||||
Margin="0 0 8 0"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"/>
|
||||
<AutoCompleteBox
|
||||
ItemsSource="{Binding RequestPaths}"
|
||||
Text="{Binding LcuRequest.RequestPath}"
|
||||
Grid.Column="0">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<AutoCompleteBox Name="RequestPathsAutoCompleteBox"
|
||||
MaxDropDownHeight="400"
|
||||
FilterMode="StartsWith"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"/>
|
||||
<Button Margin="8 0 0 0"
|
||||
<Button Name="SendRequestButton"
|
||||
Margin="8 0 0 0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
FontWeight="DemiBold"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Command="{Binding SendRequestCommand}">
|
||||
Grid.Column="2">
|
||||
Send
|
||||
</Button>
|
||||
</Grid>
|
||||
@@ -46,10 +53,10 @@
|
||||
Grid.Column="0"
|
||||
RowDefinitions="auto,*"
|
||||
ColumnDefinitions="*">
|
||||
<TextBox IsReadOnly="True"
|
||||
<TextBox Name="ResponsePathTextBox"
|
||||
IsReadOnly="True"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="{Binding LcuRequest.ResponsePath}"/>
|
||||
Grid.Column="0"/>
|
||||
<avaloniaEdit:TextEditor
|
||||
Name="RequestEditor"
|
||||
Text=""
|
||||
@@ -69,7 +76,7 @@
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0">
|
||||
<Button Content="{Binding LcuRequest.ResponseStatus}"
|
||||
<Button Name="ResponseStatusButton"
|
||||
FontSize="12"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
59
Needlework.Net/Views/Pages/Console/ConsolePage.axaml.cs
Normal file
59
Needlework.Net/Views/Pages/Console/ConsolePage.axaml.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Avalonia.Styling;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.ViewModels.Pages.Console;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Console;
|
||||
|
||||
public partial class ConsolePage : ReactiveUserControl<ConsoleViewModel>
|
||||
{
|
||||
public ConsolePage()
|
||||
{
|
||||
this.WhenAnyValue(x => x.ViewModel!.GetRequestPathsCommand)
|
||||
.SelectMany(x => x.Execute())
|
||||
.Subscribe();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
ResponseEditor.ApplyJsonEditorSettings();
|
||||
RequestEditor.ApplyJsonEditorSettings();
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.IsBusy, v => v.BusyArea.IsBusy)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.RequestMethods, v => v.RequestMethodsComboBox.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Request.Method, v => v.RequestMethodsComboBox.SelectedItem)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.RequestPaths, v => v.RequestPathsAutoCompleteBox.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Request.RequestPath, v => v.RequestPathsAutoCompleteBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Request.ResponsePath, v => v.ResponsePathTextBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Request.ResponseStatus, v => v.ResponseStatusButton.Content)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Request.RequestDocument, v => v.RequestEditor.Document)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Request.ResponseDocument, v => v.ResponseEditor.Document)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SendRequestCommand, v => v.SendRequestButton)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||
{
|
||||
var registryOptions = new RegistryOptions(
|
||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Styling;
|
||||
using AvaloniaEdit;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.ViewModels.Pages;
|
||||
using TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Views.Pages;
|
||||
|
||||
public partial class ConsoleView : UserControl
|
||||
{
|
||||
private TextEditor? _responseEditor;
|
||||
private TextEditor? _requestEditor;
|
||||
|
||||
public ConsoleView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
|
||||
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
||||
_requestEditor = this.FindControl<TextEditor>("RequestEditor");
|
||||
_responseEditor?.ApplyJsonEditorSettings();
|
||||
_requestEditor?.ApplyJsonEditorSettings();
|
||||
|
||||
var vm = (ConsoleViewModel)DataContext!;
|
||||
vm.LcuRequest.RequestText += LcuRequest_RequestText; ;
|
||||
vm.LcuRequest.UpdateText += LcuRequest_UpdateText;
|
||||
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
}
|
||||
|
||||
private void LcuRequest_RequestText(object? sender, ViewModels.Shared.LcuRequestViewModel e)
|
||||
{
|
||||
e.RequestBody = _requestEditor!.Text;
|
||||
}
|
||||
|
||||
private void LcuRequest_UpdateText(object? sender, string e)
|
||||
{
|
||||
_responseEditor!.Text = e;
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
var vm = (ConsoleViewModel)DataContext!;
|
||||
vm.LcuRequest.RequestText -= LcuRequest_RequestText;
|
||||
vm.LcuRequest.UpdateText -= LcuRequest_UpdateText;
|
||||
}
|
||||
|
||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||
{
|
||||
var registryOptions = new RegistryOptions(
|
||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointSearchDetailsView"
|
||||
x:DataType="vm:EndpointSearchDetailsViewModel">
|
||||
<Button Name="DetailsButton"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Theme="{StaticResource TransparentButton}"/>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,23 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointSearchDetailsView : ReactiveUserControl<EndpointSearchDetailsViewModel>
|
||||
{
|
||||
public EndpointSearchDetailsView()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.Plugin, v => v.DetailsButton.Content)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.OpenEndpointCommand, v => v.DetailsButton)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointTabItemContentView"
|
||||
x:DataType="vm:EndpointTabItemContentViewModel">
|
||||
<Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||
<Menu Grid.Row="0"
|
||||
Grid.Column="0">
|
||||
<MenuItem Header="_New tab">
|
||||
<MenuItem Header="LCU"
|
||||
CommandParameter="{x:Static vm:Tab.LCU}"/>
|
||||
<MenuItem Header="Game Client"
|
||||
CommandParameter="{x:Static vm:Tab.GameClient}"/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Separator Grid.Row="1"
|
||||
Grid.Column="0"/>
|
||||
<Grid Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="16"
|
||||
RowDefinitions="auto,*"
|
||||
ColumnDefinitions="*">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0 0 0 8">
|
||||
<Button Name="GoBackButton"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
Margin="0 0 8 0">
|
||||
<i:Icon Value="fa-arrow-left"
|
||||
FontSize="20"/>
|
||||
</Button>
|
||||
<TextBlock Name="TitleTextBlock"
|
||||
Theme="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{Binding Title}"/>
|
||||
</StackPanel>
|
||||
<reactiveUi:ViewModelViewHost Name="ViewModelViewHost"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,25 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointTabItemContentView : ReactiveUserControl<EndpointTabItemContentViewModel>
|
||||
{
|
||||
public EndpointTabItemContentView()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.Title, v => v.TitleTextBlock.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.ActiveViewModel, v => v.ViewModelViewHost.ViewModel)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.GoBackCommand, v => v.GoBackButton)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:views="using:Needlework.Net.Views.Pages.Endpoints"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
Name="EndpointsControl"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointTabListView"
|
||||
x:DataType="vm:EndpointTabListViewModel">
|
||||
<Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||
<TextBox Name="SearchTextBox"
|
||||
Watermark="Search"
|
||||
Margin="0 4"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"/>
|
||||
<ScrollViewer Grid.Row="2" Grid.Column="0">
|
||||
<ItemsControl Name="EndpointSearchDetailItemsControl"
|
||||
ItemsSource="{Binding Plugins}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:EndpointSearchDetailsViewModel">
|
||||
<reactiveUi:ViewModelViewHost ViewModel="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,20 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointTabListView : ReactiveUserControl<EndpointTabListViewModel>
|
||||
{
|
||||
public EndpointTabListView()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.Search, v => v.SearchTextBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Styling;
|
||||
using AvaloniaEdit;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using Needlework.Net.ViewModels.Shared;
|
||||
using TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointView : UserControl
|
||||
{
|
||||
private TextEditor? _requestEditor;
|
||||
private TextEditor? _responseEditor;
|
||||
private LcuRequestViewModel? _lcuRequestVm;
|
||||
|
||||
public EndpointView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
|
||||
_requestEditor = this.FindControl<TextEditor>("EndpointRequestEditor");
|
||||
_responseEditor = this.FindControl<TextEditor>("EndpointResponseEditor");
|
||||
_requestEditor?.ApplyJsonEditorSettings();
|
||||
_responseEditor?.ApplyJsonEditorSettings();
|
||||
|
||||
var vm = (EndpointViewModel)DataContext!;
|
||||
vm.PathOperationSelected += Vm_PathOperationSelected;
|
||||
|
||||
if (vm.SelectedPathOperation != null)
|
||||
{
|
||||
_lcuRequestVm = vm.SelectedPathOperation.LcuRequest.Value;
|
||||
vm.SelectedPathOperation.LcuRequest.Value.RequestText += LcuRequest_RequestText;
|
||||
vm.SelectedPathOperation.LcuRequest.Value.UpdateText += LcuRequest_UpdateText;
|
||||
}
|
||||
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
}
|
||||
|
||||
private void Vm_PathOperationSelected(object? sender, string e)
|
||||
{
|
||||
var vm = (EndpointViewModel)DataContext!;
|
||||
if (vm.SelectedPathOperation != null)
|
||||
{
|
||||
_requestEditor!.Text = e;
|
||||
if (_lcuRequestVm != null)
|
||||
{
|
||||
_lcuRequestVm.RequestText -= LcuRequest_RequestText;
|
||||
_lcuRequestVm.UpdateText -= LcuRequest_UpdateText;
|
||||
}
|
||||
vm.SelectedPathOperation.LcuRequest.Value.RequestText += LcuRequest_RequestText;
|
||||
vm.SelectedPathOperation.LcuRequest.Value.UpdateText += LcuRequest_UpdateText;
|
||||
_lcuRequestVm = vm.SelectedPathOperation.LcuRequest.Value;
|
||||
_responseEditor!.Text = vm.SelectedPathOperation.LcuRequest.Value.ResponseBody ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
var vm = (EndpointViewModel)DataContext!;
|
||||
vm.PathOperationSelected -= Vm_PathOperationSelected;
|
||||
|
||||
if (_lcuRequestVm != null)
|
||||
{
|
||||
_lcuRequestVm.RequestText -= LcuRequest_RequestText;
|
||||
_lcuRequestVm.UpdateText -= LcuRequest_UpdateText;
|
||||
_lcuRequestVm = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||
{
|
||||
var registryOptions = new RegistryOptions(
|
||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||
}
|
||||
|
||||
private void LcuRequest_RequestText(object? sender, LcuRequestViewModel e)
|
||||
{
|
||||
e.RequestBody = _requestEditor!.Text;
|
||||
}
|
||||
|
||||
private void LcuRequest_UpdateText(object? sender, string e)
|
||||
{
|
||||
_responseEditor!.Text = e;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointsNavigationView"
|
||||
x:DataType="vm:EndpointsNavigationViewModel">
|
||||
<Grid RowDefinitions="auto,*"
|
||||
ColumnDefinitions="*"
|
||||
Margin="16">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0 0 0 8">
|
||||
<Button Command="{Binding GoBackCommand}"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
Margin="0 0 8 0">
|
||||
<i:Icon Value="fa-arrow-left"
|
||||
FontSize="20"/>
|
||||
</Button>
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{Binding Title}"/>
|
||||
</StackPanel>
|
||||
<TransitioningContentControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Content="{Binding ActiveViewModel}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointsNavigationView : UserControl
|
||||
{
|
||||
public EndpointsNavigationView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
59
Needlework.Net/Views/Pages/Endpoints/EndpointsPage.axaml
Normal file
59
Needlework.Net/Views/Pages/Endpoints/EndpointsPage.axaml
Normal file
@@ -0,0 +1,59 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
Name="EndpointsTab"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
xmlns:views="using:Needlework.Net.Views.Pages.Endpoints"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointsPage"
|
||||
x:DataType="vm:EndpointsViewModel">
|
||||
<controls:BusyArea Name="BusyArea"
|
||||
BusyText="Loading...">
|
||||
<Grid>
|
||||
<ui:TabView Name="TabView"
|
||||
TabItems="{Binding EndpointTabItems}">
|
||||
<!--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>
|
||||
<!--We have to item template with XAML bindings here due to FluentAvalonia generating a TabViewItem control for reactive bindings...-->
|
||||
<DataTemplate x:DataType="vm:EndpointTabItemViewModel">
|
||||
<ui:TabViewItem Header="{Binding Header}"
|
||||
IconSource="{Binding IconSource}"
|
||||
IsSelected="{Binding Selected}"
|
||||
Content="{Binding Content}">
|
||||
<ui:TabViewItem.ContentTemplate>
|
||||
<DataTemplate x:DataType="vm:EndpointTabItemContentViewModel">
|
||||
<reactiveUi:ViewModelViewHost ViewModel="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ui:TabViewItem.ContentTemplate>
|
||||
</ui:TabViewItem>
|
||||
</DataTemplate>
|
||||
</ui:TabView.TabItemTemplate>
|
||||
</ui:TabView>
|
||||
</Grid>
|
||||
</controls:BusyArea>
|
||||
</UserControl>
|
||||
38
Needlework.Net/Views/Pages/Endpoints/EndpointsPage.axaml.cs
Normal file
38
Needlework.Net/Views/Pages/Endpoints/EndpointsPage.axaml.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointsPage : ReactiveUserControl<EndpointsViewModel>
|
||||
{
|
||||
public EndpointsPage()
|
||||
{
|
||||
this.WhenAnyValue(x => x.ViewModel!.GetEndpointCommand)
|
||||
.SelectMany(x => x.Execute())
|
||||
.Subscribe();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.IsBusy, v => v.BusyArea.IsBusy)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void TabView_TabCloseRequested(FluentAvalonia.UI.Controls.TabView sender, FluentAvalonia.UI.Controls.TabViewTabCloseRequestedEventArgs args)
|
||||
{
|
||||
if (args.Tab.Content is EndpointTabItemViewModel item && sender.TabItems is IList tabItems)
|
||||
{
|
||||
if (tabItems.Count > 1)
|
||||
{
|
||||
tabItems.Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointsTabView"
|
||||
x:DataType="vm:EndpointsTabViewModel">
|
||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||
BusyText="Loading...">
|
||||
<Grid>
|
||||
<ui:TabView TabItems="{Binding Endpoints}"
|
||||
AddTabButtonCommand="{Binding AddEndpointCommand}"
|
||||
TabCloseRequested="TabView_TabCloseRequested">
|
||||
<ui:TabView.TabItemTemplate>
|
||||
<DataTemplate DataType="vm:EndpointItem">
|
||||
<ui:TabViewItem Header="{Binding Header}"
|
||||
IconSource="{Binding IconSource}"
|
||||
IsSelected="{Binding Selected}"
|
||||
Content="{Binding}">
|
||||
<ui:TabViewItem.ContentTemplate>
|
||||
<DataTemplate DataType="vm:EndpointItem">
|
||||
<ContentControl Content="{Binding Content}"/>
|
||||
</DataTemplate>
|
||||
</ui:TabViewItem.ContentTemplate>
|
||||
</ui:TabViewItem>
|
||||
</DataTemplate>
|
||||
</ui:TabView.TabItemTemplate>
|
||||
</ui:TabView>
|
||||
</Grid>
|
||||
</controls:BusyArea>
|
||||
</UserControl>
|
||||
@@ -1,19 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using System.Collections;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointsTabView : UserControl
|
||||
{
|
||||
public EndpointsTabView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void TabView_TabCloseRequested(FluentAvalonia.UI.Controls.TabView sender, FluentAvalonia.UI.Controls.TabViewTabCloseRequestedEventArgs args)
|
||||
{
|
||||
if (args.Tab.Content is EndpointItem item)
|
||||
((IList)sender.TabItems).Remove(item);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
Name="EndpointsControl"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointsView"
|
||||
x:DataType="vm:EndpointsViewModel">
|
||||
<Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||
<TextBox Watermark="Search" Margin="0 4" Text="{Binding Search}" Grid.Row="1" Grid.Column="0"/>
|
||||
<ScrollViewer Grid.Row="2" Grid.Column="0">
|
||||
<ItemsRepeater ItemsSource="{Binding Query}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Command="{Binding #EndpointsControl.((vm:EndpointsViewModel)DataContext).OpenEndpointCommand}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{Binding}"
|
||||
Theme="{StaticResource TransparentButton}"/>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointsView : UserControl
|
||||
{
|
||||
public EndpointsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
37
Needlework.Net/Views/Pages/Endpoints/PathOperationView.axaml
Normal file
37
Needlework.Net/Views/Pages/Endpoints/PathOperationView.axaml
Normal file
@@ -0,0 +1,37 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.PathOperationView"
|
||||
x:DataType="vm:PathOperationViewModel">
|
||||
<Grid RowDefinitions="*"
|
||||
ColumnDefinitions="auto,*">
|
||||
<Grid.ContextFlyout>
|
||||
<MenuFlyout>
|
||||
<MenuItem Header="Copy Swagger URL" Command="{Binding CopyUrlCommand}"/>
|
||||
<MenuItem Header="Copy Markdown" Command="{Binding CopyMarkdownCommand}"/>
|
||||
</MenuFlyout>
|
||||
</Grid.ContextFlyout>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
Margin="0 0 8 0"
|
||||
Text="{Binding Request.Value.Method}"
|
||||
Background="{Binding Request.Value.Color}"
|
||||
FontSize="8"
|
||||
Width="50"
|
||||
Padding="10 2 10 2"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"/>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Path}"
|
||||
FontSize="11"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class PathOperationView : ReactiveUserControl<PathOperationViewModel>
|
||||
{
|
||||
public PathOperationView()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
// Add any activation logic here if needed
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointView"
|
||||
x:DataType="vm:EndpointViewModel">
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.PluginView"
|
||||
x:DataType="vm:PluginViewModel">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="DataGrid">
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource ControlElevationBorderBrush}"/>
|
||||
@@ -35,7 +35,7 @@
|
||||
Grid.Column="0"
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="auto,*">
|
||||
<TextBox Text="{Binding Search}"
|
||||
<TextBox Name="SearchTextBox"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"/>
|
||||
@@ -44,38 +44,11 @@
|
||||
Grid.Column="0"
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="*">
|
||||
<ListBox ItemsSource="{Binding FilteredPathOperations}"
|
||||
SelectedItem="{Binding SelectedPathOperation}"
|
||||
<ListBox Name="PathOperationListBox"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Visible"
|
||||
Margin="0 0 0 0"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="auto,*">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
Margin="0 0 8 0"
|
||||
Text="{Binding LcuRequest.Value.Method}"
|
||||
Background="{Binding LcuRequest.Value.Color}"
|
||||
FontSize="8"
|
||||
Width="50"
|
||||
Padding="10 2 10 2"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"/>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Path}"
|
||||
FontSize="11"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
Grid.Column="0"/>
|
||||
</Grid>
|
||||
<GridSplitter Background="Gray"
|
||||
Margin="8 0 8 0"
|
||||
@@ -86,44 +59,43 @@
|
||||
Grid.Column="2"
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="auto,*,auto">
|
||||
<TextBox Grid.Row="0"
|
||||
<TextBox Name="RequestMethodTextBox"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.Method}"
|
||||
FontSize="12"
|
||||
IsReadOnly="True"
|
||||
Margin="0 0 8 0"/>
|
||||
<TextBox Grid.Row="0"
|
||||
<TextBox Name="RequestResponsePathTextBox"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
FontSize="12"
|
||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponsePath}"
|
||||
IsReadOnly="True"/>
|
||||
<StackPanel Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Orientation="Horizontal">
|
||||
<Button Classes="Flat"
|
||||
<Button Name="SendRequestButton"
|
||||
Classes="Flat"
|
||||
Margin="4"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="12 4 12 4"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding SelectedPathOperation.SendRequestCommand}">Send</Button>
|
||||
VerticalAlignment="Center">Send</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" Grid.Column="2">
|
||||
<TabControl>
|
||||
<TabItem Header="Params">
|
||||
<ScrollViewer>
|
||||
<StackPanel IsVisible="{Binding SelectedPathOperation, Converter={StaticResource NullBoolConverter}}">
|
||||
<controls:Card
|
||||
Margin="0 4"
|
||||
IsVisible="{Binding SelectedPathOperation.Operation.PathParameters, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<StackPanel Name="ParamsStackPanel">
|
||||
<controls:Card Name="PathParametersCard"
|
||||
Margin="0 4">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14"
|
||||
FontWeight="DemiBold">Path Parameters</TextBlock>
|
||||
<DataGrid
|
||||
ItemsSource="{Binding SelectedPathOperation.Operation.PathParameters}"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="All">
|
||||
<DataGrid Name="PathParametersDataGrid"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="All"
|
||||
x:DataType="vm:ParameterViewModel">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
|
||||
<DataGridCheckBoxColumn Header="Required" Binding="{Binding IsRequired}"/>
|
||||
@@ -139,16 +111,15 @@
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card
|
||||
Margin="0 4"
|
||||
IsVisible="{Binding SelectedPathOperation.Operation.QueryParameters, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<controls:Card Name="QueryParametersCard"
|
||||
Margin="0 4">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14"
|
||||
FontWeight="DemiBold">Query Parameters</TextBlock>
|
||||
<DataGrid
|
||||
ItemsSource="{Binding SelectedPathOperation.Operation.QueryParameters}"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
<DataGrid Name="QueryParametersDataGrid"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal"
|
||||
x:DataType="vm:ParameterViewModel">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
|
||||
<DataGridCheckBoxColumn Header="Required" Binding="{Binding IsRequired}"/>
|
||||
@@ -184,110 +155,64 @@
|
||||
VerticalAlignment="Center">
|
||||
Username
|
||||
</TextBlock>
|
||||
<TextBox FontSize="12"
|
||||
<TextBox Name="UsernameTextBox"
|
||||
FontSize="12"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0 0 0 8"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponseUsername}" />
|
||||
IsReadOnly="True"/>
|
||||
<TextBlock FontSize="12"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center">
|
||||
Password
|
||||
</TextBlock>
|
||||
<TextBox FontSize="12"
|
||||
<TextBox Name="PasswordTextBox"
|
||||
FontSize="12"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0 0 0 8"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponsePassword}"/>
|
||||
IsReadOnly="True"/>
|
||||
<TextBlock FontSize="12"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center">
|
||||
Authorization
|
||||
</TextBlock>
|
||||
<TextBox FontSize="12"
|
||||
<TextBox Name="AuthorizationTextBox" FontSize="12"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponseAuthorization}"/>
|
||||
IsReadOnly="True"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Schemas">
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<controls:Card Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.RequestBodyType, Converter={StaticResource NullBoolConverter}}">
|
||||
<controls:Card Name="RequestBodyTypeCard"
|
||||
Margin="0 4">
|
||||
<TextBlock>
|
||||
<Run Text="Request body: " FontWeight="DemiBold" FontSize="12"/>
|
||||
<Run Text="{Binding SelectedPathOperation.Operation.RequestBodyType}" FontSize="12"/>
|
||||
<Run Name="RequestBodyTypeRun" FontSize="12"/>
|
||||
</TextBlock>
|
||||
</controls:Card>
|
||||
<Border Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.RequestClasses, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<Border Name="RequestClassesBorder" Margin="0 4">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" FontWeight="DemiBold" Margin="0 0 0 4">Request Classes</TextBlock>
|
||||
<ItemsRepeater ItemsSource="{Binding SelectedPathOperation.Operation.RequestClasses}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0 4 0 8">
|
||||
<TextBlock FontSize="12" FontWeight="DemiBold" Text="{Binding Id}"/>
|
||||
<controls:Card IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyFields}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</controls:Card>
|
||||
<controls:Card Margin="0 0 0 8" IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyEnums}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
<ItemsControl Name="RequestClassItemsControl"
|
||||
Margin="0 4 0 8"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<controls:Card Margin="0 4">
|
||||
<TextBlock>
|
||||
<Run Text="Return value: " FontWeight="DemiBold" FontSize="12"/>
|
||||
<Run Text="{Binding SelectedPathOperation.Operation.ReturnType}" FontSize="12"/>
|
||||
<Run Name="ReturnTypeRun" FontSize="12"/>
|
||||
</TextBlock>
|
||||
</controls:Card>
|
||||
<Border Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.ResponseClasses, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<Border Name="ResponseClassesBorder" Margin="0 4">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" FontWeight="DemiBold">Response Classes</TextBlock>
|
||||
<ItemsRepeater ItemsSource="{Binding SelectedPathOperation.Operation.ResponseClasses}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0 4 0 8">
|
||||
<TextBlock FontSize="12" FontWeight="DemiBold" Text="{Binding Id}" Margin="0 0 0 4"/>
|
||||
<controls:Card IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyFields}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</controls:Card>
|
||||
<controls:Card Margin="0 0 0 8" IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyEnums}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
<ItemsControl Name="ResponseClassItemsControl"
|
||||
Margin="0 4 0 8"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
@@ -298,18 +223,18 @@
|
||||
<GridSplitter Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Background="Gray"
|
||||
Margin="8 0 8 0"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="4" Orientation="Horizontal">
|
||||
<Button HorizontalAlignment="Left"
|
||||
<Button Name="ResponseStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="4"
|
||||
FontSize="10"
|
||||
Padding="12 4 12 4"
|
||||
Classes="Flat"
|
||||
Content="{Binding SelectedPathOperation.LcuRequest.Value.ResponseStatus}"/>
|
||||
Classes="Flat"/>
|
||||
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="1" Grid.Column="4">
|
||||
<controls:BusyArea BusyText="Loading..."
|
||||
IsBusy="{Binding SelectedPathOperation.IsBusy}">
|
||||
<controls:BusyArea Name="SelectedPathOperationBusyArea"
|
||||
BusyText="Loading...">
|
||||
<TabControl>
|
||||
<TabItem Header="Preview">
|
||||
<avalonEdit:TextEditor
|
||||
79
Needlework.Net/Views/Pages/Endpoints/PluginView.axaml.cs
Normal file
79
Needlework.Net/Views/Pages/Endpoints/PluginView.axaml.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Avalonia.Styling;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
using TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class PluginView : ReactiveUserControl<PluginViewModel>
|
||||
{
|
||||
public PluginView()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
EndpointRequestEditor?.ApplyJsonEditorSettings();
|
||||
EndpointResponseEditor?.ApplyJsonEditorSettings();
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Search, v => v.SearchTextBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.FilteredPathOperations, v => v.PathOperationListBox.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedPathOperation, v => v.PathOperationListBox.SelectedItem)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Request.Value.Method, v => v.RequestMethodTextBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Request.Value.ResponsePath, v => v.RequestResponsePathTextBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation, v => v.ParamsStackPanel.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.PathParameters, v => v.PathParametersCard.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.PathParameters, v => v.PathParametersDataGrid.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.QueryParameters, v => v.QueryParametersCard.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.QueryParameters, v => v.QueryParametersDataGrid.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Request.Value.ResponseUsername, v => v.UsernameTextBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Request.Value.ResponsePassword, v => v.PasswordTextBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Request.Value.ResponseAuthorization, v => v.AuthorizationTextBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.RequestBodyType, v => v.RequestBodyTypeCard.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.RequestBodyType, v => v.RequestBodyTypeRun.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.RequestClasses, v => v.RequestClassesBorder.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.RequestClasses, v => v.RequestClassItemsControl.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.ReturnType, v => v.ReturnTypeRun.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.ResponseClasses, v => v.ResponseClassesBorder.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Operation.ResponseClasses, v => v.ResponseClassItemsControl.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.Request.Value.ResponseStatus, v => v.ResponseStatusButton.Content)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.SelectedPathOperation.IsBusy, v => v.SelectedPathOperationBusyArea.IsBusy)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SelectedPathOperation.SendRequestCommand, v => v.SendRequestButton)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||
{
|
||||
var registryOptions = new RegistryOptions(
|
||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||
}
|
||||
}
|
||||
31
Needlework.Net/Views/Pages/Endpoints/PropertyClassView.axaml
Normal file
31
Needlework.Net/Views/Pages/Endpoints/PropertyClassView.axaml
Normal file
@@ -0,0 +1,31 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Endpoints.PropertyClassView"
|
||||
x:DataType="vm:PropertyClassViewModel">
|
||||
<StackPanel>
|
||||
<TextBlock Name="IdTextBlock"
|
||||
FontSize="12"
|
||||
FontWeight="DemiBold"
|
||||
Margin="0 0 0 4"/>
|
||||
<controls:Card Name="PropertyFieldsCard">
|
||||
<DataGrid Name="PropertyFieldsDataGrid"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</controls:Card>
|
||||
<controls:Card Margin="0 0 0 8" Name="PropertyEnumsCard">
|
||||
<DataGrid Name="PropertyEnumsDataGrid"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||
|
||||
public partial class PropertyClassView : ReactiveUserControl<PropertyClassViewModel>
|
||||
{
|
||||
public PropertyClassView()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Id, v => v.IdTextBlock.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.PropertyFields, v => v.PropertyFieldsCard.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.PropertyFields, v => v.PropertyFieldsDataGrid.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.PropertyEnums, v => v.PropertyEnumsCard.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.PropertyEnums, v => v.PropertyEnumsDataGrid.ItemsSource)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
100
Needlework.Net/Views/Pages/Home/HomePage.axaml
Normal file
100
Needlework.Net/Views/Pages/Home/HomePage.axaml
Normal file
@@ -0,0 +1,100 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Home"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
xmlns:views="using:Needlework.Net.Views.Pages.Home"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
Name="HomeControl"
|
||||
x:Class="Needlework.Net.Views.Pages.Home.HomePage"
|
||||
x:DataType="vm:HomeViewModel">
|
||||
<!-- TOP LEVEL -->
|
||||
<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 or Game Client 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" 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>
|
||||
<Button CommandParameter="https://discord.com/channels/187652476080488449/543112946402721832" Margin="4">
|
||||
#ingame-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">
|
||||
<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>
|
||||
<!-- 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">
|
||||
<ItemsControl Name="LibraryItemsControl" ItemsSource="{Binding Libraries}">
|
||||
<ItemsControl.Styles>
|
||||
<Style Selector="views|LibraryView">
|
||||
<Setter Property="Margin" Value="0 12 0 0"/>
|
||||
</Style>
|
||||
</ItemsControl.Styles>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:LibraryViewModel">
|
||||
<views:LibraryView ViewModel="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
15
Needlework.Net/Views/Pages/Home/HomePage.axaml.cs
Normal file
15
Needlework.Net/Views/Pages/Home/HomePage.axaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.Home;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Home;
|
||||
|
||||
public partial class HomePage : ReactiveUserControl<HomeViewModel>
|
||||
{
|
||||
public HomePage()
|
||||
{
|
||||
this.WhenActivated(disposables => { });
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
25
Needlework.Net/Views/Pages/Home/LibraryView.axaml
Normal file
25
Needlework.Net/Views/Pages/Home/LibraryView.axaml
Normal file
@@ -0,0 +1,25 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Home.LibraryView">
|
||||
<StackPanel>
|
||||
<TextBlock>
|
||||
<Run Name="LanguageRun"
|
||||
FontWeight="Bold"/>
|
||||
<Bold> - </Bold>
|
||||
<Run Name="RepoRun"
|
||||
FontWeight="Bold"/>
|
||||
</TextBlock>
|
||||
<TextBlock Name="DescriptionTextBlock"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="WrapWithOverflow"
|
||||
Width="350"/>
|
||||
<Button Name="OpenUrlButton"
|
||||
Margin="0 4 0 0">
|
||||
<TextBlock Name="OpenUrlTextBlock" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
28
Needlework.Net/Views/Pages/Home/LibraryView.axaml.cs
Normal file
28
Needlework.Net/Views/Pages/Home/LibraryView.axaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using Needlework.Net.ViewModels.Pages.Home;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.Home;
|
||||
|
||||
public partial class LibraryView : ReactiveUserControl<LibraryViewModel>
|
||||
{
|
||||
public LibraryView()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Library.Language, v => v.LanguageRun.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Library.Repo, v => v.RepoRun.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Library.Description, v => v.DescriptionTextBlock.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Library.Description, v => v.DescriptionTextBlock.IsVisible)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Library.Link, v => v.OpenUrlButton.Content)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.HomeView"
|
||||
x:DataType="vm:HomeViewModel">
|
||||
<!-- 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>
|
||||
</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">
|
||||
<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>
|
||||
</UserControl>
|
||||
@@ -1,11 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Views.Pages;
|
||||
|
||||
public partial class HomeView : UserControl
|
||||
{
|
||||
public HomeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user