Compare commits
12 Commits
47d02a61fb
...
release
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
309ac8e8f0 | ||
|
|
943234464e | ||
|
|
8681cd0b39 | ||
|
|
e2bfac458d | ||
|
|
9ba36a5d7e | ||
|
|
9c0bbc2be0 | ||
|
|
0d077e351d | ||
|
|
1f20c5a286 | ||
|
|
41e76c5d10 | ||
|
|
38ebed976d | ||
|
|
6875fbaa66 | ||
|
|
0ddcfb47a6 |
46
.github/workflows/release.yml
vendored
@@ -1,12 +1,9 @@
|
||||
# .github/workflows/release.yml
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
env:
|
||||
PROJECT_NAME: Needlework.Net
|
||||
ASSEMBLY_NAME: NeedleworkDotNet
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -18,15 +15,30 @@ jobs:
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: dotnet build ${{env.PROJECT_NAME}} -c Release
|
||||
- name: Publish
|
||||
run: dotnet publish ${{env.PROJECT_NAME}} -c Release -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -p:DebugSymbols=false -o publish -r win-x64 --self-contained=false
|
||||
- name: Zip files
|
||||
run: 7z a -tzip ${{env.ASSEMBLY_NAME}}-win-x64.zip ./Publish/* README.md LICENSE
|
||||
- name: Upload to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
files: ${{env.ASSEMBLY_NAME}}-win-x64.zip
|
||||
fetch-depth: 0
|
||||
ref: release
|
||||
- name: Build
|
||||
run: dotnet build Needlework.Net -c Release
|
||||
- name: Publish
|
||||
run: dotnet publish Needlework.Net -c Release -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -p:DebugSymbols=false -o publish -r win-x64 --self-contained=false
|
||||
- name: Get Version
|
||||
id: version
|
||||
shell: powershell
|
||||
run: |
|
||||
$xml=[xml](Get-Content .\Needlework.Net\Needlework.Net.csproj)
|
||||
$ver=($xml.Project.PropertyGroup).AssemblyVersion
|
||||
$ver="VERSION=$ver"
|
||||
$ver=$ver -replace '\s',''
|
||||
echo $ver >> $env:GITHUB_OUTPUT
|
||||
- name: Zip Files
|
||||
run: 7z a -tzip NeedleworkDotNet-win-x64.zip ./Publish/* README.md LICENSE
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: "Needlework.Net v${{ steps.version.outputs.VERSION }}"
|
||||
prerelease: false
|
||||
tag_name: "${{ steps.version.outputs.VERSION }}"
|
||||
files: |
|
||||
NeedleworkDotNet-win-x64.zip
|
||||
5
.gitignore
vendored
@@ -34,7 +34,6 @@ bld/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
[Dd]ata/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
@@ -483,7 +482,3 @@ $RECYCLE.BIN/
|
||||
|
||||
# Vim temporary swap files
|
||||
*.swp
|
||||
|
||||
*.sqlite
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
|
||||
@@ -7,17 +7,20 @@
|
||||
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" />
|
||||
<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>
|
||||
|
||||
<Application.Resources>
|
||||
<converters:EnumerableToVisibility x:Key="EnumerableToVisibilityConverter"/>
|
||||
<converters:NullableToVisibility x:Key="NullableToVisibilityConverter"/>
|
||||
<converters:EnumerableBoolConverter x:Key="EnumerableBoolConverter"/>
|
||||
<converters:NullBoolConverter x:Key="NullBoolConverter"/>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -1,43 +1,18 @@
|
||||
using Akavache;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Needlework.Net.Constants;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.ViewModels.MainWindow;
|
||||
using Needlework.Net.ViewModels.Pages;
|
||||
using Needlework.Net.Views.MainWindow;
|
||||
using Needlework.Net.ViewModels;
|
||||
using Needlework.Net.Views;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net;
|
||||
|
||||
public partial class App : Application, IEnableLogger
|
||||
public partial class App(IServiceProvider serviceProvider) : Application
|
||||
{
|
||||
private readonly IDataTemplate _viewLocator;
|
||||
|
||||
private readonly IBlobCache _blobCache;
|
||||
|
||||
private readonly PageFactory _pageFactory;
|
||||
|
||||
private readonly MainWindowViewModel _mainWindowViewModel;
|
||||
|
||||
public App(IServiceProvider serviceProvider)
|
||||
{
|
||||
_viewLocator = serviceProvider.GetRequiredService<IDataTemplate>();
|
||||
_blobCache = serviceProvider.GetRequiredService<IBlobCache>();
|
||||
_pageFactory = serviceProvider.GetRequiredService<PageFactory>();
|
||||
_mainWindowViewModel = serviceProvider.GetRequiredService<MainWindowViewModel>();
|
||||
|
||||
this.Log()
|
||||
.Debug("NeedleworkDotNet version: {Version}", AppInfo.Version);
|
||||
this.Log()
|
||||
.Debug("OS description: {Description}", System.Runtime.InteropServices.RuntimeInformation.OSDescription);
|
||||
}
|
||||
private readonly IServiceProvider _serviceProvider = serviceProvider;
|
||||
|
||||
public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
|
||||
{
|
||||
@@ -51,7 +26,6 @@ public partial class App : Application, IEnableLogger
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
DataTemplates.Add(_viewLocator);
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
@@ -59,15 +33,15 @@ public partial class App : Application, IEnableLogger
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindowView(_mainWindowViewModel, _pageFactory);
|
||||
MainWindow = desktop.MainWindow;
|
||||
desktop.ShutdownRequested += (_, _) =>
|
||||
desktop.MainWindow = new MainWindow()
|
||||
{
|
||||
_blobCache.Flush().Wait();
|
||||
_blobCache.Dispose();
|
||||
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
||||
};
|
||||
MainWindow = desktop.MainWindow;
|
||||
}
|
||||
|
||||
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
BIN
Needlework.Net/Assets/Icons/home.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Needlework.Net/Assets/Icons/info-circle.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Needlework.Net/Assets/Icons/list-alt.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Needlework.Net/Assets/Icons/plug.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Needlework.Net/Assets/Icons/terminal.png
Normal file
|
After Width: | Height: | Size: 938 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 79 KiB |
@@ -1,11 +0,0 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Needlework.Net.Constants
|
||||
{
|
||||
public static class AppInfo
|
||||
{
|
||||
public static readonly string Name = "Needlework.Net";
|
||||
|
||||
public static readonly string Version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Needlework.Net.Constants
|
||||
{
|
||||
public static class BlobCacheKeys
|
||||
{
|
||||
public static readonly string GithubLatestRelease = nameof(GithubLatestRelease);
|
||||
|
||||
public static readonly string AppSettings = nameof(AppSettings);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Needlework.Net.Constants
|
||||
{
|
||||
public static class FlurlClientKeys
|
||||
{
|
||||
public static readonly string GithubClient = nameof(GithubClient);
|
||||
|
||||
public static readonly string GithubUserContentClient = nameof(GithubUserContentClient);
|
||||
|
||||
public static readonly string Client = nameof(Client);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.Constants
|
||||
{
|
||||
public static class Intervals
|
||||
{
|
||||
public static readonly TimeSpan CheckForUpdates = TimeSpan.FromMinutes(60);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@
|
||||
<!-- Set Defaults -->
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border Padding="12"
|
||||
CornerRadius="4"
|
||||
<Border Padding="16"
|
||||
CornerRadius="16,16,16,16"
|
||||
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}">
|
||||
<ContentPresenter Content="{TemplateBinding Content}"/>
|
||||
</Border>
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<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="4"
|
||||
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>
|
||||
@@ -1,91 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Converters
|
||||
{
|
||||
public class EnumerableToVisibility : IValueConverter
|
||||
public class EnumerableBoolConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
@@ -4,7 +4,7 @@ using System.Globalization;
|
||||
|
||||
namespace Needlework.Net.Converters
|
||||
{
|
||||
public class NullableToVisibility : IValueConverter
|
||||
public class NullBoolConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
@@ -1,13 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.DataModels
|
||||
{
|
||||
public partial class AppSettings : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private bool _isCheckForUpdates = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isCheckForSchema = true;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Needlework.Net.DataModels
|
||||
{
|
||||
public class HextechDocsPost
|
||||
{
|
||||
public required string Path { get; init; }
|
||||
|
||||
public required string Title { get; init; }
|
||||
|
||||
public required string Excerpt { get; init; }
|
||||
|
||||
public string Url => $"https://hextechdocs.dev{Path}";
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Serilog;
|
||||
|
||||
namespace Needlework.Net.Extensions
|
||||
{
|
||||
public static class EnableLoggerExtensions
|
||||
{
|
||||
private static readonly ILogger _logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.File(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}", path: "Logs/debug-.log", rollingInterval: RollingInterval.Day, shared: true)
|
||||
.CreateLogger();
|
||||
|
||||
public static ILogger Log(this IEnableLogger? context) => _logger.ForContext(context?.GetType() ?? typeof(Program));
|
||||
}
|
||||
|
||||
public interface IEnableLogger;
|
||||
}
|
||||
22
Needlework.Net/Extensions/ServiceCollectionExtensions.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Needlework.Net/Messages/ContentRequestMessage.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class ContentRequestMessage : RequestMessage<string>
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Needlework.Net/Messages/DataReadyMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Models;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class DataReadyMessage(OpenApiDocumentWrapper wrapper) : ValueChangedMessage<OpenApiDocumentWrapper>(wrapper)
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Needlework.Net/Messages/DataRequestMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Models;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class DataRequestMessage : RequestMessage<OpenApiDocumentWrapper>
|
||||
{
|
||||
}
|
||||
}
|
||||
20
Needlework.Net/Messages/EditorUpdateMessage.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class EditorUpdateMessage(EditorUpdate editorUpdate) : ValueChangedMessage<EditorUpdate>(editorUpdate)
|
||||
{
|
||||
}
|
||||
|
||||
public class EditorUpdate
|
||||
{
|
||||
public string Text { get; }
|
||||
public string Key { get; }
|
||||
|
||||
public EditorUpdate(string text, string key)
|
||||
{
|
||||
Text = text;
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Needlework.Net/Messages/HostDocumentRequestMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class HostDocumentRequestMessage : RequestMessage<OpenApiDocument>
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Needlework.Net/Messages/InfoBarUpdateMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.ViewModels;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class InfoBarUpdateMessage(InfoBarViewModel vm) : ValueChangedMessage<InfoBarViewModel>(vm)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Needlework.Net.DataModels
|
||||
namespace Needlework.Net.Models
|
||||
{
|
||||
public class GithubRelease
|
||||
{
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; } = string.Empty;
|
||||
|
||||
public bool IsLatest(string assemblyVersion) => int.Parse(TagName.Replace(".", "")) > int.Parse(assemblyVersion.ToString().Replace(".", ""));
|
||||
public bool IsLatest(int version) => int.Parse(TagName.Replace(".", "")) > version;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Needlework.Net.Models;
|
||||
|
||||
public class Library
|
||||
{
|
||||
[JsonPropertyName("repo")]
|
||||
public required string Repo { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("language")]
|
||||
public required string Language { get; init; }
|
||||
|
||||
[JsonPropertyName("owner")]
|
||||
public required string Owner { get; init; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public List<string> Tags { get; init; } = [];
|
||||
|
||||
public string Link
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Owner.Equals("jellies")) return $"https://github.com/elliejs/{Repo}";
|
||||
return $"https://github.com/{Owner}/{Repo}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Models;
|
||||
|
||||
public class Document
|
||||
public class OpenApiDocumentWrapper
|
||||
{
|
||||
internal OpenApiDocument OpenApiDocument { get; }
|
||||
|
||||
@@ -14,7 +13,7 @@ public class Document
|
||||
|
||||
public List<string> Paths => [.. OpenApiDocument.Paths.Keys];
|
||||
|
||||
public Document(OpenApiDocument openApiDocument)
|
||||
public OpenApiDocumentWrapper(OpenApiDocument openApiDocument)
|
||||
{
|
||||
OpenApiDocument = openApiDocument;
|
||||
var plugins = new SortedDictionary<string, List<PathOperation>>();
|
||||
@@ -36,10 +35,10 @@ public class Document
|
||||
{
|
||||
pluginsKey = "default";
|
||||
if (plugins.TryGetValue(pluginsKey, out var p))
|
||||
p.Add(new(method.ToString(), path, pluginsKey, operation));
|
||||
p.Add(new(method.ToString(), path, operation));
|
||||
else
|
||||
{
|
||||
operations.Add(new(method.ToString(), path, pluginsKey, operation));
|
||||
operations.Add(new(method.ToString(), path, operation));
|
||||
plugins[pluginsKey] = operations;
|
||||
}
|
||||
}
|
||||
@@ -47,16 +46,19 @@ public class Document
|
||||
{
|
||||
foreach (var tag in operation.Tags)
|
||||
{
|
||||
if (tag.Name == "plugins")
|
||||
var lowercaseTag = tag.Name.ToLower();
|
||||
if (lowercaseTag == "plugins")
|
||||
continue;
|
||||
else if (lowercaseTag.Contains("plugin "))
|
||||
pluginsKey = lowercaseTag.Replace("plugin ", "");
|
||||
else
|
||||
pluginsKey = tag.Name;
|
||||
pluginsKey = lowercaseTag;
|
||||
|
||||
if (plugins.TryGetValue(pluginsKey, out var p))
|
||||
p.Add(new(method.ToString(), path, tag.Name, operation));
|
||||
p.Add(new(method.ToString(), path, operation));
|
||||
else
|
||||
{
|
||||
operations.Add(new(method.ToString(), path, tag.Name, operation));
|
||||
operations.Add(new(method.ToString(), path, operation));
|
||||
plugins[pluginsKey] = operations;
|
||||
}
|
||||
}
|
||||
@@ -64,10 +66,6 @@ public class Document
|
||||
}
|
||||
}
|
||||
|
||||
plugins = new(plugins.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value.OrderBy(x => x.Path).ToList()));
|
||||
|
||||
Plugins = plugins;
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,4 @@ using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Models;
|
||||
|
||||
public record PathOperation(string Method, string Path, string Tag, OpenApiOperation Operation);
|
||||
public record PathOperation(string Method, string Path, OpenApiOperation Operation);
|
||||
23
Needlework.Net/Models/Resources.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
namespace Needlework.Net.Models
|
||||
{
|
||||
public record SchemaPaneItem(string Key, Tab Tab)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
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</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
@@ -11,57 +11,39 @@
|
||||
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
||||
<AssemblyVersion>0.13.1.0</AssemblyVersion>
|
||||
<AssemblyVersion>0.7.0.0</AssemblyVersion>
|
||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="akavache" Version="10.2.41" />
|
||||
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
||||
<PackageReference Include="Avalonia" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.8" />
|
||||
<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.Controls.ItemsRepeater" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.3" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.8" />
|
||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.3.0" />
|
||||
<PackageReference Include="BlossomiShymae.Briar" Version="0.2.3" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="DebounceThrottle" Version="3.0.1" />
|
||||
<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.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="9.0.3" />
|
||||
<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="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.69" />
|
||||
<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.13.1" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.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.17" />
|
||||
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.17" />
|
||||
<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.62" />
|
||||
</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" />
|
||||
</ItemGroup>
|
||||
@@ -70,31 +52,13 @@
|
||||
<Compile Update="Controls\BusyArea.axaml.cs">
|
||||
<DependentUpon>BusyArea.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\MainWindow\MainWindowView.axaml.cs">
|
||||
<DependentUpon>MainWindowView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\EndpointListView.axaml.cs">
|
||||
<DependentUpon>EndpointListView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\EndpointsView.axaml.cs">
|
||||
<DependentUpon>EndpointsView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Endpoints\PluginView.axaml.cs">
|
||||
<DependentUpon>PluginView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\Schemas\SchemaSearchDetailsView.axaml.cs">
|
||||
<DependentUpon>SchemaSearchDetailsView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\Pages\WebSocket\WebsocketView.axaml.cs">
|
||||
<DependentUpon>WebSocketView.axaml</DependentUpon>
|
||||
<Compile Update="Views\EndpointView.axaml.cs">
|
||||
<DependentUpon>EndpointView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\Users\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="ViewModels\Pages\Schemas\SchemaSearchDetailsViewModel.cs" />
|
||||
<Folder Include="Utilities\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,32 +1,10 @@
|
||||
using Akavache;
|
||||
using Akavache.Sqlite3;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Flurl.Http.Configuration;
|
||||
using Avalonia;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Needlework.Net.Constants;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.MainWindow;
|
||||
using Needlework.Net.ViewModels.Pages;
|
||||
using Needlework.Net.ViewModels.Pages.About;
|
||||
using Needlework.Net.ViewModels.Pages.Console;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using Needlework.Net.ViewModels.Pages.Home;
|
||||
using Needlework.Net.ViewModels.Pages.Schemas;
|
||||
using Needlework.Net.ViewModels.Pages.Settings;
|
||||
using Needlework.Net.ViewModels.Pages.WebSocket;
|
||||
using Needlework.Net.Views.MainWindow;
|
||||
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.Schemas;
|
||||
using Needlework.Net.Views.Pages.Settings;
|
||||
using Needlework.Net.Views.Pages.WebSocket;
|
||||
using Needlework.Net.ViewModels;
|
||||
using Projektanker.Icons.Avalonia;
|
||||
using Projektanker.Icons.Avalonia.FontAwesome;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
@@ -43,19 +21,18 @@ class Program
|
||||
AppDomain.CurrentDomain.UnhandledException += Program_UnhandledException;
|
||||
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
IconProvider.Current.Register<FontAwesomeIconProvider>();
|
||||
IconProvider.Current
|
||||
.Register<FontAwesomeIconProvider>();
|
||||
|
||||
return AppBuilder.Configure(() => new App(BuildServices()))
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.With(new Win32PlatformOptions { CompositionMode = [Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition] })
|
||||
.With(new MacOSPlatformOptions { ShowInDock = true, })
|
||||
.LogToTrace();
|
||||
}
|
||||
|
||||
@@ -63,90 +40,18 @@ class Program
|
||||
{
|
||||
var builder = new ServiceCollection();
|
||||
|
||||
AddViews(builder);
|
||||
AddViewModels(builder);
|
||||
AddServices(builder);
|
||||
|
||||
return builder.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private static void AddViews(ServiceCollection builder)
|
||||
{
|
||||
var locator = new ViewLocator();
|
||||
// MAIN WINDOW
|
||||
locator.Register<NotificationViewModel>(() => new NotificationView());
|
||||
locator.Register<ViewModels.MainWindow.SchemaSearchDetailsViewModel>(() => new Views.MainWindow.SchemaSearchDetailsView());
|
||||
locator.Register<SchemaViewModel>(() => new SchemaView());
|
||||
// ABOUT
|
||||
locator.Register<AboutViewModel>(() => new AboutView());
|
||||
// CONSOLE
|
||||
locator.Register<ConsoleViewModel>(() => new ConsoleView());
|
||||
// ENDPOINTS
|
||||
locator.Register<EndpointListViewModel>(() => new EndpointListView());
|
||||
locator.Register<EndpointSearchDetailsViewModel>(() => new EndpointSearchDetailsView());
|
||||
locator.Register<EndpointsViewModel>(() => new EndpointsView());
|
||||
locator.Register<EndpointTabItemContentViewModel>(() => new EndpointTabItemContentView());
|
||||
locator.Register<PathOperationViewModel>(() => new PathOperationView());
|
||||
locator.Register<PluginViewModel>(() => new PluginView());
|
||||
locator.Register<PropertyClassViewModel>(() => new PropertyClassView());
|
||||
// HOME
|
||||
locator.Register<HomeViewModel>(() => new HomeView());
|
||||
locator.Register<LibraryViewModel>(() => new LibraryView());
|
||||
locator.Register<HextechDocsPostViewModel>(() => new HextechDocsPostView());
|
||||
// SCHEMAS
|
||||
locator.Register<SchemasViewModel>(() => new SchemasView());
|
||||
locator.Register<ViewModels.Pages.Schemas.SchemaSearchDetailsViewModel>(() => new Views.Pages.Schemas.SchemaSearchDetailsView());
|
||||
// WEBSOCKET
|
||||
locator.Register<WebSocketViewModel>(() => new WebSocketView());
|
||||
locator.Register<EventViewModel>(() => new EventView());
|
||||
// SETTINGS
|
||||
locator.Register<SettingsViewModel>(() => new SettingsView());
|
||||
|
||||
builder.AddSingleton<IDataTemplate>(locator);
|
||||
}
|
||||
|
||||
private static void AddServices(ServiceCollection builder)
|
||||
{
|
||||
builder.AddSingleton<DialogService>();
|
||||
builder.AddSingleton<DocumentService>();
|
||||
builder.AddSingleton<NotificationService>();
|
||||
builder.AddSingleton<SchemaPaneService>();
|
||||
builder.AddSingleton<HextechDocsService>();
|
||||
builder.AddSingleton<GithubService>();
|
||||
builder.AddSingleton<IBlobCache>((_) =>
|
||||
{
|
||||
var appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
appDataFolder = string.IsNullOrEmpty(appDataFolder) ? "AppData" : appDataFolder;
|
||||
var appFolder = Path.Join(appDataFolder, AppInfo.Name);
|
||||
Directory.CreateDirectory(appFolder);
|
||||
var filePath = Path.Join(appFolder, "cache.sqlite");
|
||||
return new SqlRawPersistentBlobCache(filePath);
|
||||
});
|
||||
builder.AddSingleton<IFlurlClientCache>(new FlurlClientCache()
|
||||
.Add(FlurlClientKeys.GithubClient, "https://api.github.com")
|
||||
.Add(FlurlClientKeys.GithubUserContentClient, "https://raw.githubusercontent.com")
|
||||
.Add(FlurlClientKeys.Client));
|
||||
|
||||
builder.AddLogging((builder) => builder.AddSerilog(EnableLoggerExtensions.Log(null)));
|
||||
}
|
||||
|
||||
private static void AddViewModels(ServiceCollection builder)
|
||||
{
|
||||
builder.AddSingleton<MainWindowViewModel>();
|
||||
builder.AddSingleton<DialogService>();
|
||||
builder.AddSingletonsFromAssemblies<PageBase>();
|
||||
|
||||
builder.AddSingleton<PageBase, HomeViewModel>();
|
||||
builder.AddSingleton<PageBase, ConsoleViewModel>();
|
||||
builder.AddSingleton<PageBase, EndpointsViewModel>();
|
||||
builder.AddSingleton<PageBase, WebSocketViewModel>();
|
||||
builder.AddSingleton<PageBase, SchemasViewModel>();
|
||||
builder.AddSingleton<PageBase, AboutViewModel>();
|
||||
builder.AddSingleton<PageBase, SettingsViewModel>();
|
||||
builder.AddHttpClient();
|
||||
|
||||
builder.AddSingleton<PageFactory>();
|
||||
var services = builder.BuildServiceProvider();
|
||||
return services;
|
||||
}
|
||||
|
||||
private static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
File.WriteAllText($"Logs/fatal-{DateTime.Now:yyyyMMdd}.log", e.ExceptionObject.ToString());
|
||||
File.WriteAllText($"errorlog-{DateTime.Now:HHmmssfff}", e.ExceptionObject.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
using FastCache;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Microsoft.OpenApi.Readers;
|
||||
using Needlework.Net.Constants;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Models;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net
|
||||
{
|
||||
public class DocumentService : IEnableLogger
|
||||
{
|
||||
private readonly OpenApiStreamReader _reader = new();
|
||||
|
||||
private readonly IFlurlClient _githubUserContentClient;
|
||||
|
||||
public DocumentService(IFlurlClientCache clients)
|
||||
{
|
||||
_githubUserContentClient = clients.Get(FlurlClientKeys.GithubUserContentClient);
|
||||
}
|
||||
|
||||
public async Task<Document> GetLcuSchemaDocumentAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (Cached<Document>.TryGet(nameof(GetLcuSchemaDocumentAsync), out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var lcuSchemaStream = await _githubUserContentClient.Request("/dysolix/hasagi-types/main/swagger.json")
|
||||
.GetStreamAsync(cancellationToken: cancellationToken);
|
||||
var lcuSchemaRaw = _reader.Read(lcuSchemaStream, out var diagnostic);
|
||||
foreach (var error in diagnostic.Errors)
|
||||
{
|
||||
this.Log()
|
||||
.Warning("Diagnostic error: {Message}", error);
|
||||
}
|
||||
var document = new Document(lcuSchemaRaw);
|
||||
|
||||
return cached.Save(document, TimeSpan.FromMinutes(60));
|
||||
}
|
||||
|
||||
public async Task<Document> GetLolClientDocumentAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
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(cancellationToken: cancellationToken);
|
||||
var lolClientRaw = _reader.Read(lolClientStream, out var diagnostic);
|
||||
foreach (var error in diagnostic.Errors)
|
||||
{
|
||||
this.Log()
|
||||
.Warning("Diagnostic error: {Message}", error);
|
||||
}
|
||||
var document = new Document(lolClientRaw);
|
||||
|
||||
return cached.Save(document, TimeSpan.FromMinutes(60));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using Akavache;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Needlework.Net.Constants;
|
||||
using Needlework.Net.DataModels;
|
||||
using Needlework.Net.Extensions;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Services
|
||||
{
|
||||
public class GithubService : IEnableLogger
|
||||
{
|
||||
private readonly IFlurlClient _githubClient;
|
||||
|
||||
private readonly IFlurlClient _githubUserContentClient;
|
||||
|
||||
private readonly IBlobCache _blobCache;
|
||||
|
||||
public GithubService(IBlobCache blobCache, IFlurlClientCache clients)
|
||||
{
|
||||
_githubClient = clients.Get(FlurlClientKeys.GithubClient);
|
||||
_githubUserContentClient = clients.Get(FlurlClientKeys.GithubUserContentClient);
|
||||
_blobCache = blobCache;
|
||||
}
|
||||
|
||||
public async Task<GithubRelease> GetLatestReleaseAsync()
|
||||
{
|
||||
return await _blobCache.GetOrFetchObject(BlobCacheKeys.GithubLatestRelease, async () =>
|
||||
{
|
||||
this.Log()
|
||||
.Debug("Downloading latest release info from GitHub...");
|
||||
var release = await _githubClient
|
||||
.Request("/repos/BlossomiShymae/Needlework.Net/releases/latest")
|
||||
.WithHeader("User-Agent", $"{AppInfo.Name}/{AppInfo.Version}")
|
||||
.GetJsonAsync<GithubRelease>();
|
||||
return release;
|
||||
}, DateTimeOffset.Now + Intervals.CheckForUpdates);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using Akavache;
|
||||
using AngleSharp;
|
||||
using Needlework.Net.DataModels;
|
||||
using Needlework.Net.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Services
|
||||
{
|
||||
public class HextechDocsService : IEnableLogger
|
||||
{
|
||||
private readonly IBrowsingContext _context = BrowsingContext.New(Configuration.Default.WithDefaultLoader());
|
||||
|
||||
private readonly IBlobCache _blobCache;
|
||||
|
||||
public HextechDocsService(IBlobCache blobCache)
|
||||
{
|
||||
_blobCache = blobCache;
|
||||
}
|
||||
|
||||
public async Task<List<HextechDocsPost>> GetPostsAsync()
|
||||
{
|
||||
return await _blobCache.GetOrFetchObject("HextechDocsPosts", async () =>
|
||||
{
|
||||
this.Log()
|
||||
.Debug("Downloading HextechDocs posts...");
|
||||
var document = await _context.OpenAsync("https://hextechdocs.dev/tag/lcu/");
|
||||
var elements = document.QuerySelectorAll("article.post-card");
|
||||
var posts = new List<HextechDocsPost>();
|
||||
foreach (var element in elements)
|
||||
{
|
||||
var path = element.QuerySelector("a.post-card-content-link")!.GetAttribute("href")!;
|
||||
var title = element.QuerySelector(".post-card-title")!.TextContent;
|
||||
var excerpt = element.QuerySelector(".post-card-excerpt > p")!.TextContent;
|
||||
var post = new HextechDocsPost()
|
||||
{
|
||||
Path = path,
|
||||
Title = title,
|
||||
Excerpt = excerpt,
|
||||
};
|
||||
posts.Add(post);
|
||||
}
|
||||
return posts;
|
||||
}, DateTimeOffset.Now + TimeSpan.FromHours(12));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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)
|
||||
{
|
||||
var notification = new Notification(title, message, severity, duration, url);
|
||||
_notificationSubject.OnNext(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using System;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace Needlework.Net.Services
|
||||
{
|
||||
public class SchemaPaneService
|
||||
{
|
||||
private readonly Subject<SchemaPaneItem> _schemaPaneItemsSubject = new();
|
||||
|
||||
public IObservable<SchemaPaneItem> SchemaPaneItems { get { return _schemaPaneItemsSubject; } }
|
||||
|
||||
public void Add(string key, Tab tab)
|
||||
{
|
||||
var schemaPaneItem = new SchemaPaneItem(key, tab);
|
||||
_schemaPaneItemsSubject.OnNext(schemaPaneItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,30 @@ namespace Needlework.Net
|
||||
{
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
private readonly Dictionary<Type, Func<Control>> _viewRegister = [];
|
||||
|
||||
public void Register<T>(Func<Control> viewActivator)
|
||||
where T : INotifyPropertyChanged
|
||||
{
|
||||
_viewRegister[typeof(T)] = viewActivator;
|
||||
}
|
||||
private readonly Dictionary<object, Control> _controlCache = [];
|
||||
|
||||
public Control Build(object? data)
|
||||
{
|
||||
if (!_viewRegister.TryGetValue(data!.GetType(), out var activator))
|
||||
var fullName = data?.GetType().FullName;
|
||||
if (fullName is null)
|
||||
{
|
||||
throw new Exception("Data type has no registered view activator.");
|
||||
return new TextBlock { Text = "Data is null or has no name." };
|
||||
}
|
||||
|
||||
var res = activator();
|
||||
res!.DataContext = data;
|
||||
var name = fullName.Replace("ViewModel", "View");
|
||||
var type = Type.GetType(name);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
26
Needlework.Net/ViewModels/AboutViewModel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Needlework.Net/ViewModels/ConsoleViewModel.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using Avalonia.Collections;
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
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 bool _isRequestBusy = false;
|
||||
[ObservableProperty] private string? _requestMethodSelected = "GET";
|
||||
[ObservableProperty] private string? _requestPath = null;
|
||||
[ObservableProperty] private string? _requestBody = null;
|
||||
[ObservableProperty] private string? _responsePath = null;
|
||||
[ObservableProperty] private string? _responseStatus = null;
|
||||
[ObservableProperty] private string? _responseAuthorization = null;
|
||||
|
||||
public ConsoleViewModel() : base("Console", "terminal", -200)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SendRequest()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsRequestBusy = true;
|
||||
if (string.IsNullOrEmpty(RequestPath)) throw new Exception("Path is empty.");
|
||||
|
||||
var method = RequestMethodSelected 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."),
|
||||
};
|
||||
|
||||
var processInfo = ProcessFinder.Get();
|
||||
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "ConsoleRequestEditor").Response;
|
||||
var content = new StringContent(requestBody, 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));
|
||||
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
|
||||
}
|
||||
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(body), nameof(ConsoleViewModel));
|
||||
|
||||
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
|
||||
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
|
||||
ResponseAuthorization = $"Basic {riotAuthentication.Value}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
||||
ResponseStatus = null;
|
||||
ResponsePath = null;
|
||||
ResponseAuthorization = null;
|
||||
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRequestBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(DataReadyMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
RequestPaths.Clear();
|
||||
RequestPaths.AddRange(message.Value.Paths);
|
||||
IsBusy = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Needlework.Net/ViewModels/EndpointViewModel.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
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 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;
|
||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(value.Operation.RequestTemplate ?? string.Empty, "EndpointRequestEditor")));
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Needlework.Net/ViewModels/EndpointsContainerViewModel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class EndpointsContainerViewModel : PageBase
|
||||
{
|
||||
[ObservableProperty] private ObservableObject _activeViewModel;
|
||||
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
||||
[ObservableProperty] private string _title = string.Empty;
|
||||
|
||||
public EndpointsContainerViewModel(HttpClient httpClient) : base("Endpoints", "list-alt", -500)
|
||||
{
|
||||
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(httpClient, OnClicked);
|
||||
}
|
||||
|
||||
private void OnClicked(ObservableObject viewModel)
|
||||
{
|
||||
ActiveViewModel = viewModel;
|
||||
if (viewModel is EndpointViewModel endpoint) Title = endpoint.Title;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void GoBack()
|
||||
{
|
||||
ActiveViewModel = EndpointsViewModel;
|
||||
Title = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Needlework.Net/ViewModels/EndpointsViewModel.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class EndpointsViewModel : ObservableObject, IRecipient<DataReadyMessage>
|
||||
{
|
||||
public HttpClient HttpClient { get; }
|
||||
|
||||
public string Title => "Endpoints";
|
||||
public Action<ObservableObject> OnClicked;
|
||||
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
|
||||
public IAvaloniaList<string> Query { get; } = new AvaloniaList<string>();
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
[ObservableProperty] private string _search = string.Empty;
|
||||
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
||||
|
||||
public EndpointsViewModel(HttpClient httpClient, Action<ObservableObject> onClicked)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
OnClicked = onClicked;
|
||||
|
||||
WeakReferenceMessenger.Default.Register(this);
|
||||
}
|
||||
|
||||
public void Receive(DataReadyMessage message)
|
||||
{
|
||||
IsBusy = false;
|
||||
Plugins.Clear();
|
||||
Plugins.AddRange(message.Value.Plugins.Keys);
|
||||
Query.Clear();
|
||||
Query.AddRange(Plugins);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Needlework.Net/ViewModels/EventViewModel.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public class EventViewModel : ObservableObject
|
||||
{
|
||||
public string Time { get; }
|
||||
public string Type { get; }
|
||||
public string Uri { get; }
|
||||
|
||||
public string Key => $"{Time} {Type} {Uri}";
|
||||
|
||||
public EventViewModel(EventData eventData)
|
||||
{
|
||||
Time = $"{DateTime.Now:HH:mm:ss.fff}";
|
||||
Type = eventData?.EventType.ToUpper() ?? string.Empty;
|
||||
Uri = eventData?.Uri ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Needlework.Net/ViewModels/HomeViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Needlework.Net/ViewModels/InfoBarViewModel.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
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,201 +0,0 @@
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Constants;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Helpers;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.Views.MainWindow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow;
|
||||
|
||||
public partial class MainWindowViewModel
|
||||
: ObservableObject, IRecipient<OopsiesDialogRequestedMessage>, IEnableLogger
|
||||
{
|
||||
private readonly DocumentService _documentService;
|
||||
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
private readonly DialogService _dialogService;
|
||||
|
||||
private readonly SchemaPaneService _schemaPaneService;
|
||||
|
||||
public MainWindowViewModel(DialogService dialogService, DocumentService documentService, NotificationService notificationService, SchemaPaneService schemaPaneService)
|
||||
{
|
||||
_dialogService = dialogService;
|
||||
_documentService = documentService;
|
||||
_notificationService = notificationService;
|
||||
_schemaPaneService = schemaPaneService;
|
||||
|
||||
_notificationService.Notifications.Subscribe(async notification =>
|
||||
{
|
||||
var vm = new NotificationViewModel(notification);
|
||||
Notifications.Add(vm);
|
||||
await Task.Delay(notification.Duration ?? TimeSpan.FromSeconds(10));
|
||||
Notifications.Remove(vm);
|
||||
});
|
||||
|
||||
_schemaPaneService.SchemaPaneItems.Subscribe(async item =>
|
||||
{
|
||||
var document = item.Tab switch
|
||||
{
|
||||
Pages.Endpoints.Tab.LCU => await documentService.GetLcuSchemaDocumentAsync(),
|
||||
Pages.Endpoints.Tab.GameClient => await documentService.GetLolClientDocumentAsync(),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
var propertyClassViewModel = OpenApiHelpers.WalkSchema(document.OpenApiDocument.Components.Schemas[item.Key], document.OpenApiDocument);
|
||||
var schemaViewModel = new SchemaViewModel(propertyClassViewModel);
|
||||
if (Schemas.ToList().Find(schema => schema.Id == schemaViewModel.Id) == null)
|
||||
{
|
||||
Schemas.Add(schemaViewModel);
|
||||
IsPaneOpen = true;
|
||||
|
||||
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
});
|
||||
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isPaneOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<SchemaViewModel> _schemas = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private SchemaViewModel? _selectedSchema;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<NotificationViewModel> _notifications = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private SchemaSearchDetailsViewModel? _selectedSchemaSearchDetails;
|
||||
|
||||
public string AppName => AppInfo.Name;
|
||||
|
||||
public string Title => $"{AppInfo.Name} {AppInfo.Version}";
|
||||
|
||||
partial void OnSelectedSchemaSearchDetailsChanged(SchemaSearchDetailsViewModel? value)
|
||||
{
|
||||
if (value == null) return;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var document = value.Tab switch
|
||||
{
|
||||
Pages.Endpoints.Tab.LCU => await _documentService.GetLcuSchemaDocumentAsync(),
|
||||
Pages.Endpoints.Tab.GameClient => await _documentService.GetLolClientDocumentAsync(),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
var propertyClassViewModel = OpenApiHelpers.WalkSchema(document.OpenApiDocument.Components.Schemas[value.Key], document.OpenApiDocument);
|
||||
var schemaViewModel = new SchemaViewModel(propertyClassViewModel);
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (Schemas.ToList().Find(schema => schema.Id == schemaViewModel.Id) == null)
|
||||
{
|
||||
Schemas.Add(schemaViewModel);
|
||||
IsPaneOpen = true;
|
||||
|
||||
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
partial void OnSelectedSchemaChanged(SchemaViewModel? value)
|
||||
{
|
||||
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
partial void OnSchemasChanged(ObservableCollection<SchemaViewModel> value)
|
||||
{
|
||||
if (!value.Any())
|
||||
{
|
||||
IsPaneOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<object>> PopulateAsync(string? searchText, CancellationToken cancellationToken)
|
||||
{
|
||||
if (searchText == null) return [];
|
||||
|
||||
var lcuSchemaDocument = await _documentService.GetLcuSchemaDocumentAsync(cancellationToken);
|
||||
var gameClientDocument = await _documentService.GetLolClientDocumentAsync(cancellationToken);
|
||||
var lcuResults = lcuSchemaDocument.OpenApiDocument.Components.Schemas.Keys.Where(key => key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(key => new SchemaSearchDetailsViewModel(key, Pages.Endpoints.Tab.LCU));
|
||||
var gameClientResults = gameClientDocument.OpenApiDocument.Components.Schemas.Keys.Where(key => key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(key => new SchemaSearchDetailsViewModel(key, Pages.Endpoints.Tab.GameClient));
|
||||
|
||||
return Enumerable.Concat(lcuResults, gameClientResults);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanOpenSchemaPane))]
|
||||
private void OpenSchemaPane()
|
||||
{
|
||||
IsPaneOpen = !IsPaneOpen;
|
||||
}
|
||||
|
||||
private bool CanOpenSchemaPane()
|
||||
{
|
||||
return Schemas.Any();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanCloseSchema))]
|
||||
private void CloseSchema()
|
||||
{
|
||||
if (SelectedSchema is SchemaViewModel selection)
|
||||
{
|
||||
SelectedSchema = null;
|
||||
Schemas = new ObservableCollection<SchemaViewModel>(Schemas.Where(schema => schema != selection));
|
||||
|
||||
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanCloseSchema()
|
||||
{
|
||||
return SelectedSchema != null;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanCloseSchemaAll))]
|
||||
private void CloseSchemaAll()
|
||||
{
|
||||
SelectedSchema = null;
|
||||
Schemas = [];
|
||||
|
||||
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool CanCloseSchemaAll()
|
||||
{
|
||||
return Schemas.Any();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process() { StartInfo = new ProcessStartInfo(url) { UseShellExecute = true } };
|
||||
process.Start();
|
||||
}
|
||||
|
||||
public void Receive(OopsiesDialogRequestedMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () => await _dialogService.ShowAsync<OopsiesDialog>(message.Value));
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Needlework.Net.Models;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow
|
||||
{
|
||||
public partial class NotificationViewModel : ObservableObject
|
||||
{
|
||||
public NotificationViewModel(Notification notification)
|
||||
{
|
||||
Notification = notification;
|
||||
IsButtonVisible = !string.IsNullOrEmpty(notification.Url);
|
||||
}
|
||||
|
||||
public bool IsButtonVisible { get; }
|
||||
|
||||
public Notification Notification { get; }
|
||||
|
||||
[RelayCommand]
|
||||
public void OpenUrl()
|
||||
{
|
||||
var process = new Process() { StartInfo = new() { UseShellExecute = true } };
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow
|
||||
{
|
||||
public partial class SchemaSearchDetailsViewModel : ObservableObject
|
||||
{
|
||||
public SchemaSearchDetailsViewModel(string key, Tab tab)
|
||||
{
|
||||
Tab = tab;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
|
||||
public Tab Tab { get; }
|
||||
|
||||
public string Document => Tab switch
|
||||
{
|
||||
Tab.LCU => "LCU",
|
||||
Tab.GameClient => "Game Client",
|
||||
_ => throw new System.NotImplementedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow
|
||||
{
|
||||
public partial class SchemaViewModel : ObservableObject
|
||||
{
|
||||
public SchemaViewModel(PropertyClassViewModel vm)
|
||||
{
|
||||
Id = vm.Id;
|
||||
PropertyFields = vm.PropertyFields;
|
||||
PropertyEnums = vm.PropertyEnums;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public List<PropertyFieldViewModel> PropertyFields { get; } = [];
|
||||
|
||||
public List<PropertyEnumViewModel> PropertyEnums { get; } = [];
|
||||
}
|
||||
}
|
||||
157
Needlework.Net/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.Views;
|
||||
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.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class MainWindowViewModel
|
||||
: ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
|
||||
{
|
||||
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
|
||||
[NotifyPropertyChangedFor(nameof(CurrentPage))]
|
||||
[ObservableProperty] private NavigationViewItem _selectedMenuItem;
|
||||
public PageBase CurrentPage => (PageBase)SelectedMenuItem.Tag!;
|
||||
|
||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||
[ObservableProperty] private bool _isUpdateShown = false;
|
||||
|
||||
public HttpClient HttpClient { get; }
|
||||
public DialogService DialogService { get; }
|
||||
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
|
||||
public OpenApiDocument? HostDocument { get; set; }
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
|
||||
[ObservableProperty] private ObservableCollection<InfoBarViewModel> _infoBarItems = [];
|
||||
|
||||
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService)
|
||||
{
|
||||
MenuItems = new AvaloniaList<NavigationViewItem>(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];
|
||||
|
||||
HttpClient = httpClient;
|
||||
DialogService = dialogService;
|
||||
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
|
||||
Task.Run(FetchDataAsync);
|
||||
new Thread(ProcessEvents) { IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
private void ProcessEvents(object? obj)
|
||||
{
|
||||
while (!IsUpdateShown)
|
||||
{
|
||||
Task.Run(CheckLatestVersionAsync);
|
||||
|
||||
Thread.Sleep(TimeSpan.FromSeconds(60));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckLatestVersionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/BlossomiShymae/Needlework.Net/releases/latest");
|
||||
request.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("Needlework.Net", Version));
|
||||
|
||||
var response = await HttpClient.SendAsync(request);
|
||||
var release = await response.Content.ReadFromJsonAsync<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;
|
||||
});
|
||||
}
|
||||
}
|
||||
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()
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,56 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Messages;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net.Helpers
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public static class OpenApiHelpers
|
||||
public partial class OperationViewModel : ObservableObject
|
||||
{
|
||||
public static string GetReturnType(OpenApiResponses responses)
|
||||
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)
|
||||
{
|
||||
if (!TryGetResponse(responses, out var response))
|
||||
return "none";
|
||||
|
||||
if (TryGetApplicationJsonMedia(response, out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
|
||||
return "none";
|
||||
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);
|
||||
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
|
||||
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
|
||||
RequestBodyType = GetRequestBodyType(operation.RequestBody);
|
||||
RequestTemplate = GetRequestTemplate(operation.RequestBody);
|
||||
}
|
||||
|
||||
public static bool TryGetApplicationJsonMedia(OpenApiResponse response, [NotNullWhen(true)] out OpenApiMediaType? media) // Because GetLolGameflowV1SpectateDelayedLaunch has an empty schema with no type...
|
||||
private string? GetRequestTemplate(OpenApiRequestBody? requestBody)
|
||||
{
|
||||
var flag = false;
|
||||
if (response.Content.TryGetValue("application/json", out var _media))
|
||||
var requestClasses = GetRequestClasses(requestBody);
|
||||
if (requestClasses.Count == 0)
|
||||
{
|
||||
if (_media?.Schema?.Type != null)
|
||||
{
|
||||
media = _media;
|
||||
flag = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
media = null;
|
||||
}
|
||||
var type = GetRequestBodyType(requestBody);
|
||||
if (type == null) return null;
|
||||
return GetRequestDefaultValue(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
media = null;
|
||||
}
|
||||
return flag;
|
||||
|
||||
var template = CreateTemplate(requestClasses);
|
||||
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
public static bool TryGetApplicationJsonMedia(OpenApiRequestBody requestBody, [NotNullWhen(true)] out OpenApiMediaType? media)
|
||||
{
|
||||
var flag = false;
|
||||
if (requestBody.Content.TryGetValue("application/json", out var _media))
|
||||
{
|
||||
if (_media?.Schema?.Type != null)
|
||||
{
|
||||
media = _media;
|
||||
flag = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
media = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
media = null;
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
public static string GetSchemaType(OpenApiSchema? schema)
|
||||
{
|
||||
if (schema == null) return "object"; // Because GetLolVanguardV1Notification exists where it has a required parameter without a type...
|
||||
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.AdditionalProperties?.Type != null) return $"{schema.AdditionalProperties.Type}[]";
|
||||
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}[]";
|
||||
return schema.Type;
|
||||
}
|
||||
|
||||
public static List<string> CreateTemplate(List<PropertyClassViewModel> requestClasses)
|
||||
private List<string> CreateTemplate(AvaloniaList<PropertyClassViewModel> requestClasses)
|
||||
{
|
||||
if (requestClasses.Count == 0) return [];
|
||||
List<string> template = [];
|
||||
@@ -115,7 +83,7 @@ namespace Needlework.Net.Helpers
|
||||
}
|
||||
else
|
||||
{
|
||||
List<PropertyClassViewModel> classes = [.. requestClasses];
|
||||
AvaloniaList<PropertyClassViewModel> classes = [.. requestClasses];
|
||||
classes.Remove(rootClass);
|
||||
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
||||
}
|
||||
@@ -129,62 +97,7 @@ namespace Needlework.Net.Helpers
|
||||
return template;
|
||||
}
|
||||
|
||||
public static string GetComponentId(OpenApiSchema schema)
|
||||
{
|
||||
string componentId;
|
||||
if (schema.Reference != null) componentId = schema.Reference.Id;
|
||||
else if (schema.Items != null) componentId = schema.Items.Reference.Id;
|
||||
else componentId = schema.AdditionalProperties.Reference.Id;
|
||||
return componentId;
|
||||
}
|
||||
|
||||
public static List<ParameterViewModel> GetParameters(List<OpenApiParameter> parameters, ParameterLocation location)
|
||||
{
|
||||
var pathParameters = new List<ParameterViewModel>();
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
if (parameter.In != location) continue;
|
||||
pathParameters.Add(new ParameterViewModel(parameter.Name, GetSchemaType(parameter.Schema), parameter.Required));
|
||||
}
|
||||
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
public static string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
||||
{
|
||||
if (requestBody == null) return null;
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return null; // Because "PostLolAccountVerificationV1SendDeactivationPin" exists where the media body is empty...
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document document)
|
||||
{
|
||||
if (requestBody == null) return [];
|
||||
if (TryGetApplicationJsonMedia(requestBody, out var media))
|
||||
{
|
||||
var rawDocument = document.OpenApiDocument;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
var type = GetSchemaType(media.Schema);
|
||||
if (IsComponent(type))
|
||||
{
|
||||
var componentId = GetComponentId(schema);
|
||||
var componentSchema = rawDocument.Components.Schemas[componentId];
|
||||
List<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(componentSchema, propertyClasses, rawDocument);
|
||||
return propertyClasses;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public static string GetRequestDefaultValue(string type)
|
||||
private static string GetRequestDefaultValue(string type)
|
||||
{
|
||||
var defaultValue = string.Empty;
|
||||
if (type.Contains("[]")) defaultValue = "[]";
|
||||
@@ -196,68 +109,45 @@ namespace Needlework.Net.Helpers
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document)
|
||||
private string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
||||
{
|
||||
var requestClasses = GetRequestClasses(requestBody, document);
|
||||
if (requestClasses.Count == 0)
|
||||
if (requestBody == null) return null;
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var type = GetRequestBodyType(requestBody);
|
||||
if (type == null) return null;
|
||||
return GetRequestDefaultValue(type);
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return null; // Because "PostLolAccountVerificationV1SendDeactivationPin" exists where the media body is empty...
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
|
||||
var template = CreateTemplate(requestClasses);
|
||||
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document document)
|
||||
private AvaloniaList<ParameterViewModel> GetParameters(IList<OpenApiParameter> parameters, ParameterLocation location)
|
||||
{
|
||||
if (!TryGetResponse(responses, out var response))
|
||||
return [];
|
||||
|
||||
if (TryGetApplicationJsonMedia(response, out var media))
|
||||
var pathParameters = new AvaloniaList<ParameterViewModel>();
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var rawDocument = document.OpenApiDocument;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
List<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(schema, propertyClasses, rawDocument);
|
||||
return propertyClasses;
|
||||
if (parameter.In != location) continue;
|
||||
pathParameters.Add(new ParameterViewModel(parameter.Name, GetSchemaType(parameter.Schema), parameter.Required));
|
||||
}
|
||||
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses)
|
||||
{
|
||||
if (responses.TryGetValue("2XX", out var response)
|
||||
&& response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
||||
var schema = media.Schema;
|
||||
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(schema, propertyClasses, document);
|
||||
return propertyClasses;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public static bool IsComponent(string type)
|
||||
{
|
||||
return !(type.Contains("object")
|
||||
|| type.Contains("array")
|
||||
|| type.Contains("bool")
|
||||
|| type.Contains("string")
|
||||
|| type.Contains("integer")
|
||||
|| type.Contains("number"));
|
||||
}
|
||||
|
||||
public static bool TryGetResponse(OpenApiResponses responses, [NotNullWhen(true)] out OpenApiResponse? response)
|
||||
{
|
||||
response = null;
|
||||
var flag = false;
|
||||
if (responses.TryGetValue("2XX", out var x))
|
||||
{
|
||||
response = x;
|
||||
flag = true;
|
||||
}
|
||||
else if (responses.TryGetValue("200", out var y))
|
||||
{
|
||||
response = y;
|
||||
flag = true;
|
||||
}
|
||||
return flag;
|
||||
|
||||
}
|
||||
|
||||
public static void WalkSchema(OpenApiSchema schema, List<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||
private void WalkSchema(OpenApiSchema schema, AvaloniaList<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||
{
|
||||
var type = GetSchemaType(schema);
|
||||
if (IsComponent(type))
|
||||
@@ -277,12 +167,67 @@ namespace Needlework.Net.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public static PropertyClassViewModel WalkSchema(OpenApiSchema schema, OpenApiDocument document)
|
||||
private static string GetComponentId(OpenApiSchema schema)
|
||||
{
|
||||
string componentId = GetComponentId(schema);
|
||||
var componentSchema = document.Components.Schemas[componentId];
|
||||
var propertyClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
||||
return propertyClass;
|
||||
string componentId;
|
||||
if (schema.Reference != null) componentId = schema.Reference.Id;
|
||||
else if (schema.Items != null) componentId = schema.Items.Reference.Id;
|
||||
else componentId = schema.AdditionalProperties.Reference.Id;
|
||||
return componentId;
|
||||
}
|
||||
|
||||
private static bool IsComponent(string type)
|
||||
{
|
||||
return !(type.Contains("object")
|
||||
|| type.Contains("array")
|
||||
|| type.Contains("bool")
|
||||
|| type.Contains("string")
|
||||
|| type.Contains("integer")
|
||||
|| type.Contains("number"));
|
||||
}
|
||||
|
||||
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody)
|
||||
{
|
||||
if (requestBody == null) return [];
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
var type = GetSchemaType(media.Schema);
|
||||
if (IsComponent(type))
|
||||
{
|
||||
var componentId = GetComponentId(schema);
|
||||
var componentSchema = document.Components.Schemas[componentId];
|
||||
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(componentSchema, propertyClasses, document);
|
||||
return propertyClasses;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private string GetReturnType(OpenApiResponses responses)
|
||||
{
|
||||
if (responses.TryGetValue("2XX", out var response)
|
||||
&& response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
public static string GetSchemaType(OpenApiSchema schema)
|
||||
{
|
||||
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.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}[]";
|
||||
return schema.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Needlework.Net/ViewModels/PageBase.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
|
||||
public abstract partial class PageBase(string displayName, string icon, int index = 0) : ObservableValidator
|
||||
{
|
||||
[ObservableProperty] private string _displayName = displayName;
|
||||
[ObservableProperty] private string _icon = icon;
|
||||
[ObservableProperty] private int _index = index;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.About;
|
||||
|
||||
public partial class AboutViewModel : PageBase
|
||||
{
|
||||
public AboutViewModel() : base("About", "fa-solid fa-circle-info")
|
||||
{
|
||||
}
|
||||
|
||||
public override Task InitializeAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process() { StartInfo = new ProcessStartInfo(url) { UseShellExecute = true } };
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.Shared;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Console;
|
||||
|
||||
public partial class ConsoleViewModel : PageBase
|
||||
{
|
||||
private readonly DocumentService _documentService;
|
||||
|
||||
public ConsoleViewModel(DocumentService documentService, NotificationService notificationService) : base("Console", "fa-solid fa-terminal")
|
||||
{
|
||||
_request = new(notificationService, Endpoints.Tab.LCU);
|
||||
_documentService = documentService;
|
||||
}
|
||||
|
||||
public List<string> RequestMethods { get; } = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"];
|
||||
|
||||
public List<string> RequestPaths { get; } = [];
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
|
||||
[ObservableProperty] private RequestViewModel _request;
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
var document = await _documentService.GetLcuSchemaDocumentAsync();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
RequestPaths.Clear();
|
||||
RequestPaths.AddRange(document.Paths);
|
||||
});
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SendRequest()
|
||||
{
|
||||
await Request.ExecuteAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using Avalonia;
|
||||
using AvaloniaEdit.Utils;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointListViewModel : ObservableObject
|
||||
{
|
||||
private readonly Document _document;
|
||||
|
||||
private readonly Tab _tab;
|
||||
|
||||
private readonly Action<ObservableObject> _onClicked;
|
||||
|
||||
private readonly ObservableCollection<string> _plugins;
|
||||
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
public EndpointListViewModel(NotificationService notificationService, ObservableCollection<string> plugins, Action<ObservableObject> onClicked, Models.Document document, Tab tab)
|
||||
{
|
||||
_plugins = new ObservableCollection<string>(plugins);
|
||||
_document = document;
|
||||
_tab = tab;
|
||||
_onClicked = onClicked;
|
||||
_notificationService = notificationService;
|
||||
|
||||
Plugins = EndpointSearchDetails = new ObservableCollection<EndpointSearchDetailsViewModel>(plugins.Select(plugin => new EndpointSearchDetailsViewModel(notificationService, document, tab, onClicked, plugin)));
|
||||
}
|
||||
|
||||
public ObservableCollection<EndpointSearchDetailsViewModel> Plugins { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<EndpointSearchDetailsViewModel> _endpointSearchDetails = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _search = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _offset = new();
|
||||
|
||||
partial void OnSearchChanged(string value)
|
||||
{
|
||||
EndpointSearchDetails.Clear();
|
||||
if (!string.IsNullOrEmpty(Search))
|
||||
{
|
||||
EndpointSearchDetails.AddRange(_plugins.Where(plugin => plugin.Contains(value, StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(plugin => new EndpointSearchDetailsViewModel(_notificationService, _document, _tab, _onClicked, plugin)));
|
||||
}
|
||||
else
|
||||
{
|
||||
EndpointSearchDetails.AddRange(
|
||||
_plugins.Select(plugin => new EndpointSearchDetailsViewModel(_notificationService, _document, _tab, _onClicked, plugin)));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenEndpoint(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return;
|
||||
_onClicked.Invoke(new PluginViewModel(_notificationService, value, _document, _tab));
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints
|
||||
{
|
||||
public partial class EndpointSearchDetailsViewModel : ObservableObject
|
||||
{
|
||||
private readonly Document _document;
|
||||
|
||||
private readonly Tab _tab;
|
||||
|
||||
private readonly Action<ObservableObject> _onClicked;
|
||||
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
public EndpointSearchDetailsViewModel(Services.NotificationService notificationService, Document document, Tab tab, Action<ObservableObject> onClicked, string? plugin)
|
||||
{
|
||||
_document = document;
|
||||
_tab = tab;
|
||||
_onClicked = onClicked;
|
||||
_plugin = plugin;
|
||||
_notificationService = notificationService;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _plugin;
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenEndpoint()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Plugin)) return;
|
||||
_onClicked.Invoke(new PluginViewModel(_notificationService, Plugin, _document, _tab));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointTabItemContentViewModel : ObservableObject
|
||||
{
|
||||
private readonly Action<string?, Guid> _onEndpointNavigation;
|
||||
|
||||
private readonly Tab _tab;
|
||||
|
||||
public EndpointTabItemContentViewModel(Services.NotificationService notificationService, ObservableCollection<string> plugins, Action<string?, Guid> onEndpointNavigation, IAsyncRelayCommand addEndpointCommand, Models.Document document, Tab tab)
|
||||
{
|
||||
_activeViewModel = _endpointsViewModel = new EndpointListViewModel(notificationService, new ObservableCollection<string>(plugins), OnClicked, document, tab);
|
||||
_onEndpointNavigation = onEndpointNavigation;
|
||||
_tab = tab;
|
||||
_title = GetTitle(tab);
|
||||
|
||||
AddEndpointCommand = addEndpointCommand;
|
||||
}
|
||||
|
||||
public Guid Guid { get; } = Guid.NewGuid();
|
||||
|
||||
public IAsyncRelayCommand AddEndpointCommand { get; }
|
||||
|
||||
[ObservableProperty] private ObservableObject _activeViewModel;
|
||||
|
||||
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
||||
|
||||
[ObservableProperty] private string _title;
|
||||
|
||||
private string GetTitle(Tab tab)
|
||||
{
|
||||
return tab switch
|
||||
{
|
||||
Tab.LCU => "LCU",
|
||||
Tab.GameClient => "Game Client",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
private void OnClicked(ObservableObject 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);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class EndpointTabItemViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _header = string.Empty;
|
||||
public IconSource IconSource { get; set; } = new SymbolIconSource() { Symbol = Symbol.Document, FontSize = 20.0, Foreground = Avalonia.Media.Brushes.White };
|
||||
public bool Selected { get; set; } = false;
|
||||
public required EndpointTabItemContentViewModel Content { get; init; }
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using Avalonia.Threading;
|
||||
using AvaloniaEdit.Utils;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public enum Tab
|
||||
{
|
||||
LCU,
|
||||
GameClient
|
||||
}
|
||||
|
||||
public partial class EndpointsViewModel : PageBase
|
||||
{
|
||||
private readonly DocumentService _documentService;
|
||||
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
public EndpointsViewModel(DocumentService documentService, NotificationService notificationService) : base("Endpoints", "fa-solid fa-rectangle-list")
|
||||
{
|
||||
_documentService = documentService;
|
||||
_notificationService = notificationService;
|
||||
}
|
||||
|
||||
public ObservableCollection<string> Plugins { get; } = [];
|
||||
|
||||
public ObservableCollection<EndpointTabItemViewModel> Endpoints { get; } = [];
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
await AddEndpoint(Tab.LCU);
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddEndpoint(Tab tab)
|
||||
{
|
||||
Document document = tab switch
|
||||
{
|
||||
Tab.LCU => await _documentService.GetLcuSchemaDocumentAsync(),
|
||||
Tab.GameClient => await _documentService.GetLolClientDocumentAsync(),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
Plugins.Clear();
|
||||
Plugins.AddRange(document.Plugins.Keys);
|
||||
var vm = new EndpointTabItemContentViewModel(_notificationService, Plugins, OnEndpointNavigation, AddEndpointCommand, document, tab);
|
||||
Endpoints.Add(new()
|
||||
{
|
||||
Content = vm,
|
||||
Header = vm.Title,
|
||||
Selected = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void OnEndpointNavigation(string? title, Guid guid)
|
||||
{
|
||||
foreach (var endpoint in Endpoints)
|
||||
{
|
||||
if (endpoint.Content.Guid.Equals(guid))
|
||||
{
|
||||
endpoint.Header = endpoint.Content.Title;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class OperationViewModel : ObservableObject
|
||||
{
|
||||
public OperationViewModel(OpenApiOperation operation, Models.Document document)
|
||||
{
|
||||
Summary = operation.Summary ?? string.Empty;
|
||||
Description = operation.Description ?? string.Empty;
|
||||
IsRequestBody = operation.RequestBody != null;
|
||||
ReturnType = OpenApiHelpers.GetReturnType(operation.Responses);
|
||||
RequestClasses = OpenApiHelpers.GetRequestClasses(operation.RequestBody, document);
|
||||
ResponseClasses = OpenApiHelpers.GetResponseClasses(operation.Responses, document);
|
||||
PathParameters = OpenApiHelpers.GetParameters(operation.Parameters.ToList(), ParameterLocation.Path);
|
||||
QueryParameters = OpenApiHelpers.GetParameters(operation.Parameters.ToList(), ParameterLocation.Query);
|
||||
RequestBodyType = OpenApiHelpers.GetRequestBodyType(operation.RequestBody);
|
||||
RequestTemplate = OpenApiHelpers.GetRequestTemplate(operation.RequestBody, document);
|
||||
}
|
||||
|
||||
public string Summary { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public string ReturnType { get; }
|
||||
|
||||
public bool IsRequestBody { get; }
|
||||
|
||||
public string? RequestBodyType { get; }
|
||||
|
||||
public List<PropertyClassViewModel> RequestClasses { get; }
|
||||
|
||||
public List<PropertyClassViewModel> ResponseClasses { get; }
|
||||
|
||||
public List<ParameterViewModel> PathParameters { get; }
|
||||
|
||||
public List<ParameterViewModel> QueryParameters { get; }
|
||||
|
||||
public string? RequestTemplate { get; }
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class ParameterViewModel : ObservableObject
|
||||
{
|
||||
public ParameterViewModel(string name, string type, bool isRequired, string? value = null)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
IsRequired = isRequired;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public bool IsRequired { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _value = null;
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.ViewModels.Shared;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class PathOperationViewModel : ObservableObject
|
||||
{
|
||||
public PathOperationViewModel(Services.NotificationService notificationService, PathOperation pathOperation, Document document, Tab tab)
|
||||
{
|
||||
Path = pathOperation.Path;
|
||||
Operation = new OperationViewModel(pathOperation.Operation, document);
|
||||
Request = new(() => new RequestViewModel(notificationService, tab)
|
||||
{
|
||||
Method = pathOperation.Method.ToUpper(),
|
||||
RequestDocument = new(Operation.RequestTemplate ?? string.Empty)
|
||||
});
|
||||
Url = $"https://swagger.dysolix.dev/lcu/#/{Uri.EscapeDataString(pathOperation.Tag)}/{pathOperation.Operation.OperationId}";
|
||||
Markdown = $"[{pathOperation.Method.ToUpper()} {Path}]({Url})";
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public OperationViewModel Operation { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
public string Markdown { get; }
|
||||
|
||||
[ObservableProperty] private bool _isBusy;
|
||||
|
||||
[ObservableProperty] private Lazy<RequestViewModel> _request;
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SendRequest()
|
||||
{
|
||||
var sb = new StringBuilder(Path);
|
||||
foreach (var pathParameter in Operation.PathParameters)
|
||||
{
|
||||
sb.Replace($"{{{pathParameter.Name}}}", pathParameter.Value);
|
||||
}
|
||||
|
||||
var firstQueryAdded = false;
|
||||
foreach (var queryParameter in Operation.QueryParameters)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(queryParameter.Value))
|
||||
{
|
||||
sb.Append(firstQueryAdded ? '&' : '?');
|
||||
firstQueryAdded = true;
|
||||
sb.Append($"{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}");
|
||||
}
|
||||
}
|
||||
|
||||
Request.Value.RequestPath = sb.ToString();
|
||||
await Request.Value.ExecuteAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyUrl()
|
||||
{
|
||||
App.MainWindow?.Clipboard?.SetTextAsync(Url);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyMarkdown()
|
||||
{
|
||||
App.MainWindow?.Clipboard?.SetTextAsync(Markdown);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using Avalonia;
|
||||
using AvaloniaEdit.Utils;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class PluginViewModel : ObservableObject
|
||||
{
|
||||
public PluginViewModel(Services.NotificationService notificationService, string endpoint, Models.Document document, Tab tab)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
PathOperations = document.Plugins[endpoint].Select(x => new PathOperationViewModel(notificationService, x, document, tab)).ToList();
|
||||
FilteredPathOperations = new ObservableCollection<PathOperationViewModel>(PathOperations);
|
||||
}
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
public string Title => Endpoint;
|
||||
|
||||
public List<PathOperationViewModel> PathOperations { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<PathOperationViewModel> _filteredPathOperations;
|
||||
|
||||
[ObservableProperty]
|
||||
private PathOperationViewModel? _selectedPathOperation;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _search;
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _offset = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _paramsOffset = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _schemasOffset = new();
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public class PropertyClassViewModel : ObservableObject
|
||||
{
|
||||
public PropertyClassViewModel(string id, IDictionary<string, OpenApiSchema> properties, IList<IOpenApiAny> enumValue)
|
||||
{
|
||||
List<PropertyFieldViewModel> propertyFields = [];
|
||||
List<PropertyEnumViewModel> propertyEnums = [];
|
||||
foreach ((var propertyName, var propertySchema) in properties)
|
||||
{
|
||||
var type = OpenApiHelpers.GetSchemaType(propertySchema);
|
||||
var field = new PropertyFieldViewModel(propertyName, type);
|
||||
propertyFields.Add(field);
|
||||
}
|
||||
if (enumValue != null && enumValue.Any())
|
||||
{
|
||||
var propertyEnum = new PropertyEnumViewModel(enumValue);
|
||||
propertyEnums.Add(propertyEnum);
|
||||
}
|
||||
PropertyFields = propertyFields;
|
||||
PropertyEnums = propertyEnums;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public List<PropertyFieldViewModel> PropertyFields { get; } = [];
|
||||
|
||||
public List<PropertyEnumViewModel> PropertyEnums { get; } = [];
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Microsoft.OpenApi.Any;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public class PropertyEnumViewModel
|
||||
{
|
||||
public PropertyEnumViewModel(IList<IOpenApiAny> enumValue)
|
||||
{
|
||||
Values = $"[{string.Join(", ", enumValue.Select(x => $"\"{((OpenApiString)x).Value}\"").ToList())}]";
|
||||
}
|
||||
public string Type { get; } = "Enum";
|
||||
|
||||
public string Values { get; }
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public class PropertyFieldViewModel
|
||||
{
|
||||
public PropertyFieldViewModel(string name, string type)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Type { get; }
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using BlossomiShymae.Briar.Utils;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
public partial class ResponseViewModel : ObservableObject
|
||||
{
|
||||
public ResponseViewModel(string path)
|
||||
{
|
||||
Path = path;
|
||||
var processInfo = GetProcessInfo();
|
||||
if (processInfo != null)
|
||||
{
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
Path = $"https://127.0.0.1:{processInfo.AppPort}{path}";
|
||||
Username = riotAuthentication.Username;
|
||||
Password = riotAuthentication.Password;
|
||||
Authorization = $"Basic {riotAuthentication.RawValue}";
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _path;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _status;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _authentication;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _username;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _password;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _authorization;
|
||||
|
||||
private static ProcessInfo? GetProcessInfo()
|
||||
{
|
||||
if (ProcessFinder.IsActive()) return ProcessFinder.GetProcessInfo();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.DataModels;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Home
|
||||
{
|
||||
public partial class HextechDocsPostViewModel : ObservableObject
|
||||
{
|
||||
public HextechDocsPostViewModel(HextechDocsPost hextechDocsPost)
|
||||
{
|
||||
HextechDocsPost = hextechDocsPost;
|
||||
}
|
||||
|
||||
public HextechDocsPost HextechDocsPost { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Home;
|
||||
|
||||
public partial class HomeViewModel : PageBase, IEnableLogger
|
||||
{
|
||||
private readonly HextechDocsService _hextechDocsService;
|
||||
|
||||
private readonly IDisposable _carouselNextDisposable;
|
||||
|
||||
public HomeViewModel(HextechDocsService hextechDocsService) : base("Home", "fa-solid fa-house")
|
||||
{
|
||||
_hextechDocsService = hextechDocsService;
|
||||
|
||||
_carouselNextDisposable = Observable.Timer(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5))
|
||||
.Select(time => Unit.Default)
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
if (SelectedHextechDocsPost is HextechDocsPostViewModel vm)
|
||||
{
|
||||
var index = HextechDocsPosts.IndexOf(vm);
|
||||
if (index == HextechDocsPosts.Count - 1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
index += 1;
|
||||
}
|
||||
SelectedHextechDocsPost = HextechDocsPosts.ElementAt(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public List<LibraryViewModel> Libraries { get; } = JsonSerializer.Deserialize<List<Library>>(AssetLoader.Open(new Uri($"avares://NeedleworkDotNet/Assets/libraries.json")))
|
||||
!.Where(library => library.Tags.Contains("lcu") || library.Tags.Contains("ingame"))
|
||||
.Select(library => new LibraryViewModel(library))
|
||||
.ToList();
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _librariesOffset = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private List<HextechDocsPostViewModel> _hextechDocsPosts = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private HextechDocsPostViewModel? _selectedHextechDocsPost;
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var posts = await _hextechDocsService.GetPostsAsync();
|
||||
var hextechDocsPosts = posts.Select(post => new HextechDocsPostViewModel(post)).ToList();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
HextechDocsPosts = hextechDocsPosts;
|
||||
SelectedHextechDocsPost = HextechDocsPosts.First();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Log()
|
||||
.Error(ex, "Failed to get posts from HextechDocs.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.Models;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Home
|
||||
{
|
||||
public partial class LibraryViewModel : ObservableObject
|
||||
{
|
||||
public LibraryViewModel(Library library)
|
||||
{
|
||||
Library = library;
|
||||
}
|
||||
|
||||
public Library Library { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages;
|
||||
|
||||
|
||||
public abstract partial class PageBase(string displayName, string icon) : ObservableValidator
|
||||
{
|
||||
public string DisplayName { get; } = displayName;
|
||||
|
||||
public string Icon { get; } = icon;
|
||||
|
||||
public abstract Task InitializeAsync();
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages
|
||||
{
|
||||
public class PageFactory
|
||||
{
|
||||
private readonly IEnumerable<PageBase> _pages;
|
||||
|
||||
public PageFactory(IEnumerable<PageBase> pages)
|
||||
{
|
||||
_pages = pages;
|
||||
}
|
||||
|
||||
public PageBase GetPage<T>() where T : PageBase
|
||||
{
|
||||
var page = _pages.Where(page => typeof(T) == page.GetType())
|
||||
.FirstOrDefault() ?? throw new NotSupportedException(typeof(T).FullName);
|
||||
Task.Run(page.InitializeAsync);
|
||||
return page;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Schemas
|
||||
{
|
||||
public partial class SchemaSearchDetailsViewModel : ObservableObject
|
||||
{
|
||||
private readonly SchemaPaneService _schemaPaneService;
|
||||
|
||||
public SchemaSearchDetailsViewModel(Tab tab, PropertyClassViewModel vm, SchemaPaneService schemaPaneService)
|
||||
{
|
||||
_schemaPaneService = schemaPaneService;
|
||||
|
||||
Tab = tab;
|
||||
Id = vm.Id;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public Tab Tab { get; }
|
||||
|
||||
public string Document => Tab switch
|
||||
{
|
||||
Tab.LCU => "LCU",
|
||||
Tab.GameClient => "Game Client",
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
[RelayCommand]
|
||||
private void Display()
|
||||
{
|
||||
_schemaPaneService.Add(Id, Tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using DebounceThrottle;
|
||||
using Needlework.Net.Helpers;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Schemas
|
||||
{
|
||||
public partial class SchemasViewModel : PageBase
|
||||
{
|
||||
private readonly DebounceDispatcher _debounceDispatcher = new(TimeSpan.FromMilliseconds(500));
|
||||
|
||||
private readonly DocumentService _documentService;
|
||||
|
||||
private readonly SchemaPaneService _schemaPaneService;
|
||||
|
||||
private List<SchemaSearchDetailsViewModel> _schemas = [];
|
||||
|
||||
public SchemasViewModel(DocumentService documentService, SchemaPaneService schemaPaneService) : base("Schemas", "fa-solid fa-file-lines")
|
||||
{
|
||||
_documentService = documentService;
|
||||
_schemaPaneService = schemaPaneService;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isBusy = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _search;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<SchemaSearchDetailsViewModel> _schemaItems = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _offset = new();
|
||||
|
||||
partial void OnSearchChanged(string? value)
|
||||
{
|
||||
_debounceDispatcher.Debounce(() =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
SchemaItems = _schemas.ToList();
|
||||
});
|
||||
return;
|
||||
}
|
||||
var items = _schemas.Where(schema => schema.Id.Contains(value, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
Dispatcher.UIThread.Invoke(() => { SchemaItems = items; });
|
||||
});
|
||||
}
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
var lcuSchemaDocument = await _documentService.GetLcuSchemaDocumentAsync();
|
||||
var lolClientDocument = await _documentService.GetLolClientDocumentAsync();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
var schemas = Enumerable.Concat(
|
||||
lcuSchemaDocument.OpenApiDocument.Components.Schemas.Values.Select(schema => new SchemaSearchDetailsViewModel(Tab.LCU, OpenApiHelpers.WalkSchema(schema, lcuSchemaDocument.OpenApiDocument), _schemaPaneService)),
|
||||
lolClientDocument.OpenApiDocument.Components.Schemas.Values.Select(schema => new SchemaSearchDetailsViewModel(Tab.GameClient, OpenApiHelpers.WalkSchema(schema, lolClientDocument.OpenApiDocument), _schemaPaneService))
|
||||
).ToList();
|
||||
_schemas = schemas;
|
||||
SchemaItems = schemas.ToList();
|
||||
IsBusy = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
using Akavache;
|
||||
using Avalonia.Threading;
|
||||
using BlossomiShymae.Briar;
|
||||
using BlossomiShymae.Briar.Utils;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Needlework.Net.Constants;
|
||||
using Needlework.Net.DataModels;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
using System;
|
||||
using System.Net.Http.Json;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Settings
|
||||
{
|
||||
public partial class SettingsViewModel : PageBase, IEnableLogger
|
||||
{
|
||||
private readonly IBlobCache _blobCache;
|
||||
|
||||
private readonly IDisposable _checkForUpdatesDisposable;
|
||||
|
||||
private readonly IDisposable _checkForSchemaVersionDisposable;
|
||||
|
||||
private readonly GithubService _githubService;
|
||||
|
||||
private readonly DocumentService _documentService;
|
||||
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
private readonly TaskCompletionSource<bool> _initializeTaskCompletionSource = new();
|
||||
|
||||
public SettingsViewModel(IBlobCache blobCache, GithubService githubService, DocumentService documentService, NotificationService notificationService) : base("Settings", "fa-solid fa-gear")
|
||||
{
|
||||
_blobCache = blobCache;
|
||||
_githubService = githubService;
|
||||
_documentService = documentService;
|
||||
_notificationService = notificationService;
|
||||
|
||||
_checkForUpdatesDisposable = Observable.Timer(TimeSpan.Zero, Intervals.CheckForUpdates)
|
||||
.Select(time => Unit.Default)
|
||||
.Subscribe(async _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _initializeTaskCompletionSource.Task;
|
||||
if (AppSettings!.IsCheckForUpdates)
|
||||
{
|
||||
await CheckForUpdatesAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = "Failed to check for updates. Please check your internet connection or try again later.";
|
||||
this.Log()
|
||||
.Error(ex, message);
|
||||
_notificationService.Notify(AppInfo.Name, message, InfoBarSeverity.Error);
|
||||
_checkForUpdatesDisposable?.Dispose();
|
||||
}
|
||||
});
|
||||
|
||||
_checkForSchemaVersionDisposable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(5))
|
||||
.Select(time => Unit.Default)
|
||||
.Subscribe(async _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _initializeTaskCompletionSource.Task;
|
||||
if (AppSettings!.IsCheckForSchema)
|
||||
{
|
||||
await CheckForSchemaVersionAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = "Failed to check for schema version. Please check your internet connection or try again later.";
|
||||
this.Log()
|
||||
.Error(ex, message);
|
||||
_notificationService.Notify(AppInfo.Name, message, InfoBarSeverity.Error);
|
||||
_checkForSchemaVersionDisposable?.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isBusy = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private AppSettings? _appSettings;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(UpdateCheckTitle), nameof(UpdateCheckIconValue), nameof(UpdateCheckLastChecked))]
|
||||
private Guid _upToDateGuid = Guid.Empty;
|
||||
|
||||
public bool IsUpToDate { get; private set; }
|
||||
|
||||
public bool IsSchemaVersionChecked { get; private set; }
|
||||
|
||||
public string UpdateCheckTitle => IsUpToDate switch
|
||||
{
|
||||
true => "You're up to date",
|
||||
false => "You're out of date"
|
||||
};
|
||||
|
||||
public string UpdateCheckIconValue => IsUpToDate switch
|
||||
{
|
||||
true => "fa-heart-circle-check",
|
||||
false => "fa-heart-circle-exclamation"
|
||||
};
|
||||
|
||||
public string UpdateCheckLastChecked => $"Last checked: {DateTime.Now:dddd}, {DateTime.Now:T}";
|
||||
|
||||
partial void OnAppSettingsChanged(AppSettings? value)
|
||||
{
|
||||
if (AppSettings is AppSettings appSettings)
|
||||
{
|
||||
_blobCache.InsertObject(BlobCacheKeys.AppSettings, appSettings);
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
AppSettings = await _blobCache.GetObject<AppSettings>(BlobCacheKeys.AppSettings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Log()
|
||||
.Warning(ex, "Failed to get application settings.");
|
||||
AppSettings = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppSettings!.PropertyChanged += (s, e) => OnAppSettingsChanged((AppSettings?)s);
|
||||
IsBusy = false;
|
||||
_initializeTaskCompletionSource.SetResult(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CheckForUpdatesAsync()
|
||||
{
|
||||
var release = await _githubService.GetLatestReleaseAsync();
|
||||
if (release.IsLatest(AppInfo.Version))
|
||||
{
|
||||
this.Log()
|
||||
.Information("New version available: {TagName}", release.TagName);
|
||||
_notificationService.Notify(AppInfo.Name, $"New version available: {release.TagName}", InfoBarSeverity.Informational, null, "https://github.com/BlossomiShymae/Needlework.Net/releases/latest");
|
||||
_checkForUpdatesDisposable?.Dispose();
|
||||
IsUpToDate = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsUpToDate = true;
|
||||
}
|
||||
UpToDateGuid = Guid.NewGuid();
|
||||
}
|
||||
|
||||
|
||||
private async Task CheckForSchemaVersionAsync()
|
||||
{
|
||||
if (!ProcessFinder.IsPortOpen()) return;
|
||||
|
||||
var lcuSchemaDocument = await _documentService.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)
|
||||
{
|
||||
this.Log()
|
||||
.Information("LCU Schema (current): {Version}", lcuSchemaDocument.Info.Version);
|
||||
this.Log()
|
||||
.Information("LCU Schema (latest): {Version}", systemBuild.Version);
|
||||
IsSchemaVersionChecked = true;
|
||||
}
|
||||
|
||||
bool isVersionMatching = currentSemVer[0] == latestSemVer[0] && currentSemVer[1] == latestSemVer[1]; // Compare major and minor versions
|
||||
if (!isVersionMatching)
|
||||
{
|
||||
this.Log()
|
||||
.Warning("LCU Schema outdated: Current {CurrentVersion}, Latest {LatestVersion}", lcuSchemaDocument.Info.Version, systemBuild.Version);
|
||||
_notificationService.Notify(AppInfo.Name, $"LCU Schema is 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using BlossomiShymae.Briar.WebSocket.Events;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.WebSocket;
|
||||
|
||||
public class EventViewModel : ObservableObject
|
||||
{
|
||||
public EventViewModel(EventData eventData)
|
||||
{
|
||||
Time = $"{DateTime.Now:HH:mm:ss.fff}";
|
||||
Type = eventData?.EventType?.ToUpper() ?? string.Empty;
|
||||
Uri = eventData?.Uri ?? string.Empty;
|
||||
}
|
||||
|
||||
public string Time { get; }
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public string Uri { get; }
|
||||
|
||||
public string Key => $"{Time} {Type} {Uri}";
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using AvaloniaEdit.Document;
|
||||
using BlossomiShymae.Briar;
|
||||
using BlossomiShymae.Briar.WebSocket.Events;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Needlework.Net.Constants;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Websocket.Client;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.WebSocket;
|
||||
|
||||
public partial class WebSocketViewModel : PageBase, IEnableLogger
|
||||
{
|
||||
private Dictionary<string, EventMessage> _events = [];
|
||||
|
||||
private readonly IFlurlClient _githubUserContentClient;
|
||||
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
private readonly object _tokenLock = new();
|
||||
|
||||
public WebSocketViewModel(IFlurlClientCache clients, NotificationService notificationService) : base("Event Viewer", "fa-solid fa-plug")
|
||||
{
|
||||
_githubUserContentClient = clients.Get(FlurlClientKeys.GithubUserContentClient);
|
||||
_notificationService = notificationService;
|
||||
|
||||
EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
||||
}
|
||||
|
||||
public ObservableCollection<EventViewModel> EventLog { get; } = [];
|
||||
|
||||
public SemaphoreSlim EventLogLock { get; } = new(1, 1);
|
||||
|
||||
public WebsocketClient? Client { get; set; }
|
||||
|
||||
public List<IDisposable> ClientDisposables = [];
|
||||
|
||||
public CancellationTokenSource TokenSource { get; set; } = new();
|
||||
|
||||
public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _eventLogOffset = new();
|
||||
|
||||
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
||||
[ObservableProperty]
|
||||
private string _search = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isAttach = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isTail = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private EventViewModel? _selectedEventLog = null;
|
||||
|
||||
[ObservableProperty]
|
||||
private IAvaloniaList<string> _eventTypes = new AvaloniaList<string>();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _eventType = "OnJsonApiEvent";
|
||||
|
||||
[ObservableProperty]
|
||||
private TextDocument _document = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _documentOffset = new();
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
await InitializeEventTypes();
|
||||
InitializeWebsocket();
|
||||
}
|
||||
|
||||
private async Task InitializeEventTypes()
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = await _githubUserContentClient.Request("/dysolix/hasagi-types/refs/heads/main/dist/lcu-events.d.ts")
|
||||
.GetStringAsync();
|
||||
var matches = EventTypesRegex().Matches(file);
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() => EventTypes.AddRange(matches.Select(m => m.Groups[1].Value)));
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
var message = "Failed to get event types from GitHub. Please check your internet connection or try again later.";
|
||||
this.Log()
|
||||
.Error(ex, message);
|
||||
_notificationService.Notify(AppInfo.Name, message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeWebsocket()
|
||||
{
|
||||
lock (_tokenLock)
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
this.Log()
|
||||
.Debug("Disposing old connection");
|
||||
foreach (var disposable in ClientDisposables)
|
||||
disposable.Dispose();
|
||||
ClientDisposables.Clear();
|
||||
Client.Dispose();
|
||||
}
|
||||
TokenSource.Cancel();
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
while (!tokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = Connector.CreateLcuWebsocketClient();
|
||||
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 Document = new(text);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Clear()
|
||||
{
|
||||
_events.Clear();
|
||||
EventLog.Clear();
|
||||
Document = new();
|
||||
}
|
||||
|
||||
private void OnReconnection(ReconnectionInfo info)
|
||||
{
|
||||
this.Log()
|
||||
.Debug("Reconnected: {Type}", info.Type);
|
||||
}
|
||||
|
||||
private void OnDisconnection(DisconnectionInfo info)
|
||||
{
|
||||
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 () =>
|
||||
{
|
||||
if (!IsAttach) return;
|
||||
|
||||
var line = new EventViewModel(message.Data!);
|
||||
|
||||
await EventLogLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (EventLog.Count < 1000)
|
||||
{
|
||||
EventLog.Add(line);
|
||||
_events[line.Key] = message;
|
||||
}
|
||||
else
|
||||
{
|
||||
var _event = EventLog[0];
|
||||
EventLog.RemoveAt(0);
|
||||
_events.Remove(_event.Key);
|
||||
|
||||
EventLog.Add(line);
|
||||
_events[line.Key] = message;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EventLogLock.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[GeneratedRegex("\"(.*?)\":")]
|
||||
public static partial Regex EventTypesRegex();
|
||||
}
|
||||
20
Needlework.Net/ViewModels/ParameterViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class ParameterViewModel : ObservableObject
|
||||
{
|
||||
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;
|
||||
Type = type;
|
||||
IsRequired = isRequired;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
Needlework.Net/ViewModels/PathOperationViewModel.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Avalonia.Media;
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.Models;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class PathOperationViewModel : ObservableObject
|
||||
{
|
||||
public string Method { get; }
|
||||
public SolidColorBrush Color { get; }
|
||||
public string Path { get; }
|
||||
public OperationViewModel Operation { get; }
|
||||
|
||||
public ProcessInfo? ProcessInfo { get; }
|
||||
|
||||
[ObservableProperty] private bool _isBusy;
|
||||
|
||||
[ObservableProperty] private Lazy<ResponseViewModel> _response;
|
||||
|
||||
public PathOperationViewModel(PathOperation pathOperation)
|
||||
{
|
||||
Method = pathOperation.Method.ToUpper();
|
||||
Color = new SolidColorBrush(GetColor(Method));
|
||||
Path = pathOperation.Path;
|
||||
Operation = new OperationViewModel(pathOperation.Operation);
|
||||
Response = new(() => new ResponseViewModel(pathOperation.Path));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task SendRequest()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
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 missing.")
|
||||
};
|
||||
|
||||
var processInfo = ProcessFinder.Get();
|
||||
var sb = new StringBuilder(Path);
|
||||
foreach (var pathParameter in Operation.PathParameters)
|
||||
{
|
||||
sb.Replace($"{{{pathParameter.Name}}}", pathParameter.Value);
|
||||
}
|
||||
|
||||
var firstQueryAdded = false;
|
||||
foreach (var queryParameter in Operation.QueryParameters)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(queryParameter.Value))
|
||||
{
|
||||
sb.Append(firstQueryAdded ? '&' : '?');
|
||||
firstQueryAdded = true;
|
||||
sb.Append($"{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}");
|
||||
}
|
||||
}
|
||||
var uri = sb.ToString();
|
||||
|
||||
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "EndpointRequestEditor").Response;
|
||||
var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
|
||||
var client = Connector.GetLcuHttpClientInstance();
|
||||
var response = await client.SendAsync(new(method, uri) { Content = content });
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
var responseBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
var responseBody = responseBytes.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBytes), App.JsonSerializerOptions) : string.Empty;
|
||||
if (responseBody.Length >= App.MaxCharacters)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(responseBody));
|
||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(string.Empty, "EndpointResponseEditor")));
|
||||
}
|
||||
else WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(responseBody, "EndpointResponseEditor")));
|
||||
|
||||
Response.Value.Status = $"{(int)response.StatusCode} {response.StatusCode}";
|
||||
Response.Value.Path = $"https://127.0.0.1:{processInfo.AppPort}{uri}";
|
||||
Response.Value.Authentication = Response.Value.Authorization = $"Basic {riotAuthentication.Value}";
|
||||
Response.Value.Username = riotAuthentication.Username;
|
||||
Response.Value.Password = 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))));
|
||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(string.Empty, "EndpointResponseEditor")));
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
public 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.")
|
||||
};
|
||||
}
|
||||
}
|
||||
36
Needlework.Net/ViewModels/PropertyClassViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public class PropertyClassViewModel : ObservableObject
|
||||
{
|
||||
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 = [];
|
||||
foreach ((var propertyName, var propertySchema) in properties)
|
||||
{
|
||||
var type = OperationViewModel.GetSchemaType(propertySchema);
|
||||
var field = new PropertyFieldViewModel(propertyName, type);
|
||||
propertyFields.Add(field);
|
||||
}
|
||||
if (enumValue != null && enumValue.Any())
|
||||
{
|
||||
var propertyEnum = new PropertyEnumViewModel(enumValue);
|
||||
propertyEnums.Add(propertyEnum);
|
||||
}
|
||||
PropertyFields = propertyFields;
|
||||
PropertyEnums = propertyEnums;
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Needlework.Net/ViewModels/PropertyEnumViewModel.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Microsoft.OpenApi.Any;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
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())}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Needlework.Net/ViewModels/PropertyFieldViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public class PropertyFieldViewModel
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Type { get; }
|
||||
|
||||
public PropertyFieldViewModel(string name, string type)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Needlework.Net/ViewModels/ResponseViewModel.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class ResponseViewModel : ObservableObject
|
||||
{
|
||||
[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;
|
||||
var processInfo = GetProcessInfo();
|
||||
if (processInfo != null)
|
||||
{
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
Path = $"https://127.0.0.1:{processInfo.AppPort}{path}";
|
||||
Username = riotAuthentication.Username;
|
||||
Password = riotAuthentication.Password;
|
||||
Authorization = $"Basic {riotAuthentication.RawValue}";
|
||||
}
|
||||
}
|
||||
|
||||
private static ProcessInfo? GetProcessInfo()
|
||||
{
|
||||
if (ProcessFinder.IsActive()) return ProcessFinder.Get();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using AvaloniaEdit.Document;
|
||||
using BlossomiShymae.Briar;
|
||||
using BlossomiShymae.Briar.Utils;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Shared;
|
||||
|
||||
public partial class RequestViewModel : ObservableObject, IEnableLogger
|
||||
{
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
private readonly Tab _tab;
|
||||
|
||||
public RequestViewModel(NotificationService notificationService, Tab tab)
|
||||
{
|
||||
_tab = tab;
|
||||
_notificationService = notificationService;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _method = "GET";
|
||||
|
||||
[ObservableProperty]
|
||||
private SolidColorBrush _color = new(GetColor("GET"));
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isRequestBusy;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _requestPath;
|
||||
|
||||
[ObservableProperty]
|
||||
private TextDocument _requestDocument = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _requestDocumentOffset = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private TextDocument _responseDocument = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private Vector _responseDocumentOffset = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private double _responseDocumentHorizontalScrollBar;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _responseDocumentVerticalScrollBar;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _responsePath;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _responseStatus;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _responseAuthentication;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _responseUsername;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _responsePassword;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _responseAuthorization;
|
||||
|
||||
partial void OnMethodChanged(string? oldValue, string? newValue)
|
||||
{
|
||||
if (newValue == null) return;
|
||||
|
||||
Color = new(GetColor(newValue));
|
||||
}
|
||||
|
||||
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 requestBody = RequestDocument.Text;
|
||||
var content = new StringContent(requestBody, 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;
|
||||
if (body.Length > App.MaxCharacters)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body));
|
||||
ResponseDocument = new();
|
||||
}
|
||||
else
|
||||
{
|
||||
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, FluentAvalonia.UI.Controls.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 requestBody = RequestDocument.Text;
|
||||
var content = new StringContent(requestBody, 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));
|
||||
ResponseDocument = new();
|
||||
}
|
||||
else
|
||||
{
|
||||
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, FluentAvalonia.UI.Controls.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 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.")
|
||||
};
|
||||
}
|
||||
126
Needlework.Net/ViewModels/WebsocketViewModel.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Websocket.Client;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class WebsocketViewModel : PageBase
|
||||
{
|
||||
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 = [];
|
||||
|
||||
public WebsocketClient? Client { get; set; }
|
||||
|
||||
public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
||||
|
||||
public WebsocketViewModel() : base("Event Viewer", "plug", -100)
|
||||
{
|
||||
EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
||||
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
private void InitializeWebsocket()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = Connector.CreateLcuWebsocketClient();
|
||||
client.EventReceived.Subscribe(OnMessage);
|
||||
client.DisconnectionHappened.Subscribe(OnDisconnection);
|
||||
client.ReconnectionHappened.Subscribe(OnReconnection);
|
||||
|
||||
client.Start();
|
||||
client.Send(new EventMessage(RequestType.Subscribe, EventMessage.Kinds.OnJsonApiEvent));
|
||||
Client = client;
|
||||
return;
|
||||
}
|
||||
catch (Exception) { }
|
||||
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
private void Clear()
|
||||
{
|
||||
_events.Clear();
|
||||
EventLog.Clear();
|
||||
}
|
||||
|
||||
private void OnReconnection(ReconnectionInfo info)
|
||||
{
|
||||
Trace.WriteLine($"-- Reconnection --\nType{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();
|
||||
}
|
||||
|
||||
private void OnMessage(EventMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () =>
|
||||
{
|
||||
if (!IsAttach) return;
|
||||
|
||||
var line = new EventViewModel(message.Data!);
|
||||
|
||||
await EventLogLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (EventLog.Count < 1000)
|
||||
{
|
||||
EventLog.Add(line);
|
||||
_events[line.Key] = message;
|
||||
}
|
||||
else
|
||||
{
|
||||
var _event = EventLog[0];
|
||||
EventLog.RemoveAt(0);
|
||||
_events.Remove(_event.Key);
|
||||
|
||||
EventLog.Add(line);
|
||||
_events[line.Key] = message;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EventLogLock.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
167
Needlework.Net/Views/AboutView.axaml
Normal file
@@ -0,0 +1,167 @@
|
||||
<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"
|
||||
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.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,6 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Views.Pages.About;
|
||||
namespace Needlework.Net.Views;
|
||||
|
||||
public partial class AboutView : UserControl
|
||||
{
|
||||
@@ -5,10 +5,10 @@
|
||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Console"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.Pages.Console.ConsoleView"
|
||||
x:Class="Needlework.Net.Views.ConsoleView"
|
||||
x:DataType="vm:ConsoleViewModel">
|
||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||
BusyText="Loading...">
|
||||
@@ -16,16 +16,16 @@
|
||||
<Grid Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2">
|
||||
<StackPanel Margin="0 0 0 8">
|
||||
<StackPanel Margin="0 0 0 16">
|
||||
<Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto">
|
||||
<ComboBox ItemsSource="{Binding RequestMethods}"
|
||||
SelectedItem="{Binding Request.Method}"
|
||||
SelectedItem="{Binding RequestMethodSelected}"
|
||||
Margin="0 0 8 0"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"/>
|
||||
<AutoCompleteBox
|
||||
ItemsSource="{Binding RequestPaths}"
|
||||
Text="{Binding Request.RequestPath}"
|
||||
Text="{Binding RequestPath}"
|
||||
MaxDropDownHeight="400"
|
||||
FilterMode="StartsWith"
|
||||
Grid.Row="0"
|
||||
@@ -49,23 +49,17 @@
|
||||
<TextBox IsReadOnly="True"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="{Binding Request.ResponsePath}"/>
|
||||
Text="{Binding ResponsePath}"/>
|
||||
<avaloniaEdit:TextEditor
|
||||
Name="RequestEditor"
|
||||
Document="{Binding Request.RequestDocument}"
|
||||
Text=""
|
||||
ShowLineNumbers="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
Margin="0 8 0 0"
|
||||
FontSize="12"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0">
|
||||
<avaloniaEdit:TextEditor.Styles>
|
||||
<Style Selector="ScrollViewer#PART_ScrollViewer">
|
||||
<Setter Property="Offset" Value="{Binding Request.RequestDocumentOffset, Mode=TwoWay}"/>
|
||||
</Style>
|
||||
</avaloniaEdit:TextEditor.Styles>
|
||||
</avaloniaEdit:TextEditor>
|
||||
Grid.Column="0"/>
|
||||
</Grid>
|
||||
<Grid RowDefinitions="35,*"
|
||||
ColumnDefinitions="*"
|
||||
@@ -75,7 +69,7 @@
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0">
|
||||
<Button Content="{Binding Request.ResponseStatus}"
|
||||
<Button Content="{Binding ResponseStatus}"
|
||||
FontSize="12"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
@@ -83,19 +77,12 @@
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Name="ResponseEditor"
|
||||
Document="{Binding Request.ResponseDocument}"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
ShowLineNumbers="True"
|
||||
IsReadOnly="True"
|
||||
Text=""
|
||||
FontSize="12">
|
||||
<avaloniaEdit:TextEditor.Styles>
|
||||
<Style Selector="ScrollViewer#PART_ScrollViewer">
|
||||
<Setter Property="Offset" Value="{Binding Request.ResponseDocumentOffset, Mode=TwoWay}"/>
|
||||
</Style>
|
||||
</avaloniaEdit:TextEditor.Styles>
|
||||
</avaloniaEdit:TextEditor>
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</controls:BusyArea>
|
||||