mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 18:20:47 +01:00
Compare commits
24 Commits
0.6.1.0
...
38e1ea2301
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38e1ea2301 | ||
|
|
30451b8c8c | ||
|
|
05927030eb | ||
|
|
e9f99a9e28 | ||
|
|
1e838abdbf | ||
|
|
dede2e909c | ||
|
|
58556283f0 | ||
|
|
16781a4df4 | ||
|
|
569f49d484 | ||
|
|
8eabd64911 | ||
|
|
02e739e1a3 | ||
|
|
c253d00ff1 | ||
|
|
4edd71a04a | ||
|
|
a4fe10157f | ||
|
|
bc4ed78767 | ||
|
|
375285067d | ||
|
|
3ec277bdd3 | ||
|
|
c097890588 | ||
|
|
3352740733 | ||
|
|
48751efc28 | ||
|
|
b6f713c675 | ||
|
|
59619764c2 | ||
|
|
de6f9f64dd | ||
|
|
4eae0bd913 |
40
.github/workflows/release.yml
vendored
40
.github/workflows/release.yml
vendored
@@ -1,9 +1,12 @@
|
|||||||
# .github/workflows/release.yml
|
# .github/workflows/release.yml
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
release:
|
||||||
branches:
|
types: [created]
|
||||||
- release
|
|
||||||
|
env:
|
||||||
|
PROJECT_NAME: Needlework.Net
|
||||||
|
ASSEMBLY_NAME: NeedleworkDotNet
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -15,30 +18,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 8.0.x
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: release
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build Needlework.Net -c Release
|
run: dotnet build ${{env.PROJECT_NAME}} -c Release
|
||||||
- name: Publish
|
- 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
|
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: Get Version
|
- name: Zip files
|
||||||
id: version
|
run: 7z a -tzip ${{env.ASSEMBLY_NAME}}-win-x64.zip ./Publish/* README.md LICENSE
|
||||||
shell: powershell
|
- name: Upload to release
|
||||||
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
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: ${{startsWith(github.ref, 'refs/tags/') }}
|
||||||
with:
|
with:
|
||||||
name: "Needlework.Net v${{ steps.version.outputs.VERSION }}"
|
files: ${{env.ASSEMBLY_NAME}}-win-x64.zip
|
||||||
prerelease: false
|
|
||||||
tag_name: "${{ steps.version.outputs.VERSION }}"
|
|
||||||
files: |
|
|
||||||
NeedleworkDotNet-win-x64.zip
|
|
||||||
@@ -3,8 +3,8 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
using Needlework.Net.Views;
|
using Needlework.Net.Views.MainWindow;
|
||||||
using System;
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ public partial class App(IServiceProvider serviceProvider) : Application
|
|||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
desktop.MainWindow = new MainWindow()
|
desktop.MainWindow = new MainWindowView()
|
||||||
{
|
{
|
||||||
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Messages
|
|
||||||
{
|
|
||||||
public class ContentRequestMessage : RequestMessage<string>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
|
|
||||||
namespace Needlework.Net.Messages
|
namespace Needlework.Net.Messages
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
||||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||||
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
||||||
<AssemblyVersion>0.6.1.0</AssemblyVersion>
|
<AssemblyVersion>0.8.0.0</AssemblyVersion>
|
||||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||||
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -27,17 +27,17 @@
|
|||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
|
||||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" />
|
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" />
|
||||||
<PackageReference Include="BlossomiShymae.GrrrLCU" Version="0.11.1" />
|
<PackageReference Include="BlossomiShymae.GrrrLCU" Version="0.14.0" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0" />
|
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0" />
|
||||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
|
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" 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" Version="1.6.22" />
|
||||||
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.17" />
|
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.22" />
|
||||||
<PackageReference Include="Projektanker.Icons.Avalonia" Version="9.4.0" />
|
<PackageReference Include="Projektanker.Icons.Avalonia" Version="9.4.0" />
|
||||||
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.0" />
|
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.0" />
|
||||||
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.62" />
|
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.64" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -52,7 +52,13 @@
|
|||||||
<Compile Update="Controls\BusyArea.axaml.cs">
|
<Compile Update="Controls\BusyArea.axaml.cs">
|
||||||
<DependentUpon>BusyArea.axaml</DependentUpon>
|
<DependentUpon>BusyArea.axaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Update="Views\EndpointView.axaml.cs">
|
<Compile Update="Views\MainWindow\MainWindowView.axaml.cs">
|
||||||
|
<DependentUpon>MainWindowView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Views\Pages\Endpoints\EndpointsNavigationView.axaml.cs">
|
||||||
|
<DependentUpon>EndpointsNavigationView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Views\Pages\Endpoints\EndpointView.axaml.cs">
|
||||||
<DependentUpon>EndpointView.axaml</DependentUpon>
|
<DependentUpon>EndpointView.axaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
using Needlework.Net.Services;
|
using Needlework.Net.Services;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
|
using Needlework.Net.ViewModels.Pages;
|
||||||
using Projektanker.Icons.Avalonia;
|
using Projektanker.Icons.Avalonia;
|
||||||
using Projektanker.Icons.Avalonia.FontAwesome;
|
using Projektanker.Icons.Avalonia.FontAwesome;
|
||||||
using System;
|
using System;
|
||||||
|
|||||||
@@ -1,28 +1,50 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Needlework.Net
|
namespace Needlework.Net
|
||||||
{
|
{
|
||||||
public class ViewLocator : IDataTemplate
|
public class ViewLocator : IDataTemplate
|
||||||
{
|
{
|
||||||
public Control? Build(object? param)
|
private readonly Dictionary<object, Control> _controlCache = [];
|
||||||
|
|
||||||
|
public Control Build(object? data)
|
||||||
{
|
{
|
||||||
if (param is null) return new TextBlock { Text = "data was null" };
|
var name = data?.GetType().Name;
|
||||||
|
if (name is null)
|
||||||
var name = param.GetType().FullName!
|
|
||||||
.Replace("ViewModels", "Views")
|
|
||||||
.Replace("ViewModel", "View");
|
|
||||||
var type = Type.GetType(name);
|
|
||||||
|
|
||||||
if (type != null) return (Control)Activator.CreateInstance(type)!;
|
|
||||||
else return new TextBlock { Text = "Not Found: " + name };
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Match(object? data)
|
|
||||||
{
|
{
|
||||||
return data is INotifyPropertyChanged;
|
return new TextBlock { Text = "Data is null or has no name." };
|
||||||
}
|
}
|
||||||
|
if (!name.Contains("ViewModel"))
|
||||||
|
{
|
||||||
|
return new TextBlock { Text = "Data name must end with ViewModel." };
|
||||||
|
}
|
||||||
|
|
||||||
|
name = name.Replace("ViewModel", "View");
|
||||||
|
var type = Assembly.GetExecutingAssembly()
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t => t.Name == name)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (type is null)
|
||||||
|
{
|
||||||
|
return new TextBlock { Text = $"No view for {name}." };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_controlCache.TryGetValue(data!, out var res))
|
||||||
|
{
|
||||||
|
res ??= (Control)Activator.CreateInstance(type)!;
|
||||||
|
_controlCache[data!] = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.DataContext = data;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Match(object? data) => data is INotifyPropertyChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
using 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
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 = Connector.GetProcessInfo();
|
|
||||||
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "ConsoleRequestEditor").Response;
|
|
||||||
var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
|
||||||
var response = await Connector.SendAsync(method, RequestPath, 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
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")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
Needlework.Net/ViewModels/MainWindow/InfoBarViewModel.cs
Normal file
26
Needlework.Net/ViewModels/MainWindow/InfoBarViewModel.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.MainWindow;
|
||||||
|
|
||||||
|
public partial class InfoBarViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty] private string _title;
|
||||||
|
[ObservableProperty] private bool _isOpen;
|
||||||
|
[ObservableProperty] private string _message;
|
||||||
|
[ObservableProperty] private InfoBarSeverity _severity;
|
||||||
|
[ObservableProperty] private TimeSpan _duration;
|
||||||
|
[ObservableProperty] private Control? _actionButton;
|
||||||
|
|
||||||
|
public InfoBarViewModel(string title, bool isOpen, string message, InfoBarSeverity severity, TimeSpan duration, Control? actionButton = null)
|
||||||
|
{
|
||||||
|
_title = title;
|
||||||
|
_isOpen = isOpen;
|
||||||
|
_message = message;
|
||||||
|
_severity = severity;
|
||||||
|
_duration = duration;
|
||||||
|
_actionButton = actionButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
157
Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs
Normal file
157
Needlework.Net/ViewModels/MainWindow/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.ViewModels.Pages;
|
||||||
|
using Needlework.Net.Views.MainWindow;
|
||||||
|
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.MainWindow;
|
||||||
|
|
||||||
|
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.FromMinutes(10)); // Avoid tripping unauthenticated rate limits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,157 +0,0 @@
|
|||||||
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,233 +0,0 @@
|
|||||||
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.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels
|
|
||||||
{
|
|
||||||
public partial class OperationViewModel : ObservableObject
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? GetRequestTemplate(OpenApiRequestBody? requestBody)
|
|
||||||
{
|
|
||||||
var requestClasses = GetRequestClasses(requestBody);
|
|
||||||
if (requestClasses.Count == 0)
|
|
||||||
{
|
|
||||||
var type = GetRequestBodyType(requestBody);
|
|
||||||
if (type == null) return null;
|
|
||||||
return GetRequestDefaultValue(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
var template = CreateTemplate(requestClasses);
|
|
||||||
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> CreateTemplate(AvaloniaList<PropertyClassViewModel> requestClasses)
|
|
||||||
{
|
|
||||||
if (requestClasses.Count == 0) return [];
|
|
||||||
List<string> template = [];
|
|
||||||
template.Add("{");
|
|
||||||
|
|
||||||
var rootClass = requestClasses.First();
|
|
||||||
if (rootClass.PropertyEnums.Any()) return [rootClass.PropertyEnums.First().Values];
|
|
||||||
var propertyFields = rootClass.PropertyFields;
|
|
||||||
for (int i = 0; i < propertyFields.Count; i++)
|
|
||||||
{
|
|
||||||
template.Add($"\"{propertyFields[i].Name}\"");
|
|
||||||
template.Add(":");
|
|
||||||
template.Add($"#{propertyFields[i].Type}");
|
|
||||||
|
|
||||||
if (i == propertyFields.Count - 1) template.Add("}");
|
|
||||||
else template.Add(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < template.Count; i++)
|
|
||||||
{
|
|
||||||
var type = template[i];
|
|
||||||
if (!type.Contains("#")) continue;
|
|
||||||
|
|
||||||
var foundClass = requestClasses.Where(c => c.Id == type.Replace("#", string.Empty));
|
|
||||||
if (foundClass.Any())
|
|
||||||
{
|
|
||||||
if (foundClass.First().PropertyEnums.Any())
|
|
||||||
{
|
|
||||||
template[i] = string.Join(string.Empty, CreateTemplate([.. foundClass]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AvaloniaList<PropertyClassViewModel> classes = [.. requestClasses];
|
|
||||||
classes.Remove(rootClass);
|
|
||||||
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
template[i] = GetRequestDefaultValue(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetRequestDefaultValue(string type)
|
|
||||||
{
|
|
||||||
var defaultValue = string.Empty;
|
|
||||||
if (type.Contains("[]")) defaultValue = "[]";
|
|
||||||
else if (type.Contains("string")) defaultValue = "\"\"";
|
|
||||||
else if (type.Contains("boolean")) defaultValue = "false";
|
|
||||||
else if (type.Contains("integer")) defaultValue = "0";
|
|
||||||
else if (type.Contains("double") || type.Contains("float")) defaultValue = "0.0";
|
|
||||||
else if (type.Contains("object")) defaultValue = "{}";
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AvaloniaList<ParameterViewModel> GetParameters(IList<OpenApiParameter> parameters, ParameterLocation location)
|
|
||||||
{
|
|
||||||
var pathParameters = new AvaloniaList<ParameterViewModel>();
|
|
||||||
foreach (var parameter in parameters)
|
|
||||||
{
|
|
||||||
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 [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WalkSchema(OpenApiSchema schema, AvaloniaList<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
|
||||||
{
|
|
||||||
var type = GetSchemaType(schema);
|
|
||||||
if (IsComponent(type))
|
|
||||||
{
|
|
||||||
string componentId = GetComponentId(schema);
|
|
||||||
var componentSchema = document.Components.Schemas[componentId];
|
|
||||||
var responseClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
|
||||||
|
|
||||||
if (propertyClasses.Where(c => c.Id == componentId).Any()) return; // Avoid adding duplicate schemas in classes
|
|
||||||
propertyClasses.Add(responseClass);
|
|
||||||
|
|
||||||
foreach ((var _, var property) in componentSchema.Properties)
|
|
||||||
// Check for self-references like "LolLootLootOddsResponse"
|
|
||||||
// I blame dubble
|
|
||||||
if (IsComponent(GetSchemaType(property)) && componentId != GetComponentId(property))
|
|
||||||
WalkSchema(property, propertyClasses, document);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
Needlework.Net/ViewModels/Pages/AboutViewModel.cs
Normal file
25
Needlework.Net/ViewModels/Pages/AboutViewModel.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
|
public partial class AboutViewModel : PageBase
|
||||||
|
{
|
||||||
|
public HttpClient HttpClient { get; }
|
||||||
|
|
||||||
|
public AboutViewModel(HttpClient httpClient) : base("About", "info-circle")
|
||||||
|
{
|
||||||
|
HttpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenUrl(string url)
|
||||||
|
{
|
||||||
|
var process = new Process()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Needlework.Net/ViewModels/Pages/ConsoleViewModel.cs
Normal file
39
Needlework.Net/ViewModels/Pages/ConsoleViewModel.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using Needlework.Net.ViewModels.Shared;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
|
public partial class ConsoleViewModel : PageBase, IRecipient<DataReadyMessage>
|
||||||
|
{
|
||||||
|
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
||||||
|
public IAvaloniaList<string> RequestPaths { get; } = new AvaloniaList<string>();
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
[ObservableProperty] private LcuRequestViewModel _lcuRequest = new();
|
||||||
|
|
||||||
|
public ConsoleViewModel() : base("Console", "terminal", -200)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task SendRequest()
|
||||||
|
{
|
||||||
|
await LcuRequest.ExecuteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(DataReadyMessage message)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||||
|
{
|
||||||
|
RequestPaths.Clear();
|
||||||
|
RequestPaths.AddRange(message.Value.Paths);
|
||||||
|
IsBusy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public string Endpoint { get; }
|
||||||
|
public string Title => Endpoint;
|
||||||
|
|
||||||
|
|
||||||
|
public IAvaloniaReadOnlyList<PathOperationViewModel> PathOperations { get; }
|
||||||
|
[ObservableProperty] private PathOperationViewModel? _selectedPathOperation;
|
||||||
|
|
||||||
|
[ObservableProperty] private string? _search;
|
||||||
|
public IAvaloniaList<PathOperationViewModel> FilteredPathOperations { get; }
|
||||||
|
|
||||||
|
public event EventHandler<string>? PathOperationSelected;
|
||||||
|
|
||||||
|
public EndpointViewModel(string endpoint)
|
||||||
|
{
|
||||||
|
Endpoint = endpoint;
|
||||||
|
|
||||||
|
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
|
||||||
|
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x)));
|
||||||
|
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSearchChanged(string? value)
|
||||||
|
{
|
||||||
|
FilteredPathOperations.Clear();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
FilteredPathOperations.AddRange(PathOperations);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FilteredPathOperations.AddRange(PathOperations.Where(o => o.Path.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedPathOperationChanged(PathOperationViewModel? value)
|
||||||
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
PathOperationSelected?.Invoke(this, value.Operation.RequestTemplate ?? string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointsNavigationViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public Guid Guid { get; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[ObservableProperty] private ObservableObject _activeViewModel;
|
||||||
|
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
||||||
|
[ObservableProperty] private string _title = string.Empty;
|
||||||
|
|
||||||
|
private readonly Action<string?, Guid> _onEndpointNavigation;
|
||||||
|
|
||||||
|
public EndpointsNavigationViewModel(IAvaloniaList<string> plugins, Action<string?, Guid> onEndpointNavigation)
|
||||||
|
{
|
||||||
|
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(plugins, OnClicked);
|
||||||
|
_onEndpointNavigation = onEndpointNavigation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClicked(ObservableObject viewModel)
|
||||||
|
{
|
||||||
|
ActiveViewModel = viewModel;
|
||||||
|
if (viewModel is EndpointViewModel endpoint)
|
||||||
|
{
|
||||||
|
Title = endpoint.Title;
|
||||||
|
_onEndpointNavigation.Invoke(endpoint.Title, Guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void GoBack()
|
||||||
|
{
|
||||||
|
ActiveViewModel = EndpointsViewModel;
|
||||||
|
Title = string.Empty;
|
||||||
|
_onEndpointNavigation.Invoke(null, Guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointsTabViewModel : PageBase, IRecipient<DataReadyMessage>
|
||||||
|
{
|
||||||
|
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
|
||||||
|
public IAvaloniaList<EndpointItem> Endpoints { get; } = new AvaloniaList<EndpointItem>();
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
|
||||||
|
public EndpointsTabViewModel() : base("Endpoints", "list-alt", -500)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(DataReadyMessage message)
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
Plugins.Clear();
|
||||||
|
Plugins.AddRange(message.Value.Plugins.Keys);
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(AddEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void AddEndpoint()
|
||||||
|
{
|
||||||
|
Endpoints.Add(new()
|
||||||
|
{
|
||||||
|
Content = new EndpointsNavigationViewModel(Plugins, OnEndpointNavigation),
|
||||||
|
Selected = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEndpointNavigation(string? title, Guid guid)
|
||||||
|
{
|
||||||
|
foreach (var endpoint in Endpoints)
|
||||||
|
{
|
||||||
|
if (endpoint.Content.Guid.Equals(guid))
|
||||||
|
{
|
||||||
|
endpoint.Header = title ?? "Endpoints";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class EndpointItem : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty] private string _header = "Endpoints";
|
||||||
|
public IconSource IconSource { get; set; } = new SymbolIconSource() { Symbol = Symbol.Document, FontSize = 20.0, Foreground = Avalonia.Media.Brushes.White };
|
||||||
|
public bool Selected { get; set; } = false;
|
||||||
|
public required EndpointsNavigationViewModel Content { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointsViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public IAvaloniaList<string> Plugins { get; }
|
||||||
|
public IAvaloniaList<string> Query { get; }
|
||||||
|
|
||||||
|
[ObservableProperty] private string _search = string.Empty;
|
||||||
|
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
||||||
|
|
||||||
|
public Action<ObservableObject> OnClicked { get; }
|
||||||
|
|
||||||
|
public EndpointsViewModel(IAvaloniaList<string> plugins, Action<ObservableObject> onClicked)
|
||||||
|
{
|
||||||
|
Plugins = plugins;
|
||||||
|
Query = plugins;
|
||||||
|
OnClicked = onClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSearchChanged(string value)
|
||||||
|
{
|
||||||
|
Query.Clear();
|
||||||
|
if (!string.IsNullOrEmpty(Search))
|
||||||
|
Query.AddRange(Plugins.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
||||||
|
else
|
||||||
|
Query.AddRange(Plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenEndpoint(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value)) return;
|
||||||
|
|
||||||
|
OnClicked.Invoke(new EndpointViewModel(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
232
Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs
Normal file
232
Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
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.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class OperationViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetRequestTemplate(OpenApiRequestBody? requestBody)
|
||||||
|
{
|
||||||
|
var requestClasses = GetRequestClasses(requestBody);
|
||||||
|
if (requestClasses.Count == 0)
|
||||||
|
{
|
||||||
|
var type = GetRequestBodyType(requestBody);
|
||||||
|
if (type == null) return null;
|
||||||
|
return GetRequestDefaultValue(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
var template = CreateTemplate(requestClasses);
|
||||||
|
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> CreateTemplate(AvaloniaList<PropertyClassViewModel> requestClasses)
|
||||||
|
{
|
||||||
|
if (requestClasses.Count == 0) return [];
|
||||||
|
List<string> template = [];
|
||||||
|
template.Add("{");
|
||||||
|
|
||||||
|
var rootClass = requestClasses.First();
|
||||||
|
if (rootClass.PropertyEnums.Any()) return [rootClass.PropertyEnums.First().Values];
|
||||||
|
var propertyFields = rootClass.PropertyFields;
|
||||||
|
for (int i = 0; i < propertyFields.Count; i++)
|
||||||
|
{
|
||||||
|
template.Add($"\"{propertyFields[i].Name}\"");
|
||||||
|
template.Add(":");
|
||||||
|
template.Add($"#{propertyFields[i].Type}");
|
||||||
|
|
||||||
|
if (i == propertyFields.Count - 1) template.Add("}");
|
||||||
|
else template.Add(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < template.Count; i++)
|
||||||
|
{
|
||||||
|
var type = template[i];
|
||||||
|
if (!type.Contains("#")) continue;
|
||||||
|
|
||||||
|
var foundClass = requestClasses.Where(c => c.Id == type.Replace("#", string.Empty));
|
||||||
|
if (foundClass.Any())
|
||||||
|
{
|
||||||
|
if (foundClass.First().PropertyEnums.Any())
|
||||||
|
{
|
||||||
|
template[i] = string.Join(string.Empty, CreateTemplate([.. foundClass]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AvaloniaList<PropertyClassViewModel> classes = [.. requestClasses];
|
||||||
|
classes.Remove(rootClass);
|
||||||
|
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
template[i] = GetRequestDefaultValue(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRequestDefaultValue(string type)
|
||||||
|
{
|
||||||
|
var defaultValue = string.Empty;
|
||||||
|
if (type.Contains("[]")) defaultValue = "[]";
|
||||||
|
else if (type.Contains("string")) defaultValue = "\"\"";
|
||||||
|
else if (type.Contains("boolean")) defaultValue = "false";
|
||||||
|
else if (type.Contains("integer")) defaultValue = "0";
|
||||||
|
else if (type.Contains("double") || type.Contains("float")) defaultValue = "0.0";
|
||||||
|
else if (type.Contains("object")) defaultValue = "{}";
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AvaloniaList<ParameterViewModel> GetParameters(IList<OpenApiParameter> parameters, ParameterLocation location)
|
||||||
|
{
|
||||||
|
var pathParameters = new AvaloniaList<ParameterViewModel>();
|
||||||
|
foreach (var parameter in parameters)
|
||||||
|
{
|
||||||
|
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 [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WalkSchema(OpenApiSchema schema, AvaloniaList<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||||
|
{
|
||||||
|
var type = GetSchemaType(schema);
|
||||||
|
if (IsComponent(type))
|
||||||
|
{
|
||||||
|
string componentId = GetComponentId(schema);
|
||||||
|
var componentSchema = document.Components.Schemas[componentId];
|
||||||
|
var responseClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
||||||
|
|
||||||
|
if (propertyClasses.Where(c => c.Id == componentId).Any()) return; // Avoid adding duplicate schemas in classes
|
||||||
|
propertyClasses.Add(responseClass);
|
||||||
|
|
||||||
|
foreach ((var _, var property) in componentSchema.Properties)
|
||||||
|
// Check for self-references like "LolLootLootOddsResponse"
|
||||||
|
// I blame dubble
|
||||||
|
if (IsComponent(GetSchemaType(property)) && componentId != GetComponentId(property))
|
||||||
|
WalkSchema(property, propertyClasses, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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 string Path { get; }
|
||||||
|
public OperationViewModel Operation { get; }
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy;
|
||||||
|
[ObservableProperty] private Lazy<LcuRequestViewModel> _lcuRequest;
|
||||||
|
|
||||||
|
public PathOperationViewModel(PathOperation pathOperation)
|
||||||
|
{
|
||||||
|
Path = pathOperation.Path;
|
||||||
|
Operation = new OperationViewModel(pathOperation.Operation);
|
||||||
|
LcuRequest = new(() => new LcuRequestViewModel()
|
||||||
|
{
|
||||||
|
Method = pathOperation.Method.ToUpper()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[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)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LcuRequest.Value.RequestPath = sb.ToString();
|
||||||
|
await LcuRequest.Value.ExecuteAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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.Pages.Endpoints;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.OpenApi.Any;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public class PropertyEnumViewModel
|
||||||
|
{
|
||||||
|
public string Type { get; } = "Enum";
|
||||||
|
public string Values { get; }
|
||||||
|
|
||||||
|
public PropertyEnumViewModel(IList<IOpenApiAny> enumValue)
|
||||||
|
{
|
||||||
|
Values = $"[{string.Join(", ", enumValue.Select(x => $"\"{((OpenApiString)x).Value}\"").ToList())}]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
public class PropertyFieldViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Type { get; }
|
||||||
|
|
||||||
|
public PropertyFieldViewModel(string name, string type)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
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.GetProcessInfo();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Needlework.Net/ViewModels/Pages/HomeViewModel.cs
Normal file
19
Needlework.Net/ViewModels/Pages/HomeViewModel.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
|
public partial class HomeViewModel : PageBase
|
||||||
|
{
|
||||||
|
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenUrl(string url)
|
||||||
|
{
|
||||||
|
var process = new Process()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Needlework.Net/ViewModels/Pages/PageBase.cs
Normal file
11
Needlework.Net/ViewModels/Pages/PageBase.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages;
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
21
Needlework.Net/ViewModels/Pages/Websocket/EventViewModel.cs
Normal file
21
Needlework.Net/ViewModels/Pages/Websocket/EventViewModel.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Pages.Websocket;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
125
Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs
Normal file
125
Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
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.Pages.Websocket;
|
||||||
|
|
||||||
|
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(EventRequestType.Subscribe, EventKinds.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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
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 string? _responsePath;
|
|
||||||
[ObservableProperty] private string? _responseStatus;
|
|
||||||
[ObservableProperty] private string? _responseAuthentication;
|
|
||||||
[ObservableProperty] private string? _responseUsername;
|
|
||||||
[ObservableProperty] private string? _responsePassword;
|
|
||||||
[ObservableProperty] private string? _responseAuthorization;
|
|
||||||
|
|
||||||
public PathOperationViewModel(PathOperation pathOperation)
|
|
||||||
{
|
|
||||||
Method = pathOperation.Method.ToUpper();
|
|
||||||
Color = new SolidColorBrush(GetColor(Method));
|
|
||||||
Path = pathOperation.Path;
|
|
||||||
Operation = new OperationViewModel(pathOperation.Operation);
|
|
||||||
ProcessInfo = GetProcessInfo();
|
|
||||||
if (ProcessInfo != null)
|
|
||||||
{
|
|
||||||
ResponsePath = $"https://127.0.0.1:{ProcessInfo.AppPort}{Path}";
|
|
||||||
var riotAuth = new RiotAuthentication(ProcessInfo.RemotingAuthToken);
|
|
||||||
ResponseUsername = riotAuth.Username;
|
|
||||||
ResponsePassword = riotAuth.Password;
|
|
||||||
ResponseAuthorization = $"Basic {riotAuth.Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProcessInfo? GetProcessInfo()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var processInfo = Connector.GetProcessInfo();
|
|
||||||
return processInfo;
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[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 = Connector.GetProcessInfo();
|
|
||||||
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 response = await Connector.SendAsync(method, uri, 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")));
|
|
||||||
|
|
||||||
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode}";
|
|
||||||
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{uri}";
|
|
||||||
ResponseAuthentication = $"Basic {riotAuthentication.Value}";
|
|
||||||
ResponseUsername = riotAuthentication.Username;
|
|
||||||
ResponsePassword = riotAuthentication.Password;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
|
||||||
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.")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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())}]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
118
Needlework.Net/ViewModels/Shared/LcuRequestViewModel.cs
Normal file
118
Needlework.Net/ViewModels/Shared/LcuRequestViewModel.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Messages;
|
||||||
|
using Needlework.Net.ViewModels.MainWindow;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.Shared;
|
||||||
|
|
||||||
|
public partial class LcuRequestViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty] private string? _method = "GET";
|
||||||
|
[ObservableProperty] private SolidColorBrush _color = new(GetColor("GET"));
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isRequestBusy = false;
|
||||||
|
[ObservableProperty] private string? _requestPath = null;
|
||||||
|
[ObservableProperty] private string? _requestBody = null;
|
||||||
|
|
||||||
|
[ObservableProperty] private string? _responsePath = null;
|
||||||
|
[ObservableProperty] private string? _responseStatus = null;
|
||||||
|
[ObservableProperty] private string? _responseAuthentication = null;
|
||||||
|
[ObservableProperty] private string? _responseUsername = null;
|
||||||
|
[ObservableProperty] private string? _responsePassword = null;
|
||||||
|
[ObservableProperty] private string? _responseAuthorization = null;
|
||||||
|
[ObservableProperty] private string? _responseBody = null;
|
||||||
|
|
||||||
|
public event EventHandler<LcuRequestViewModel>? RequestText;
|
||||||
|
public event EventHandler<string>? UpdateText;
|
||||||
|
|
||||||
|
partial void OnMethodChanged(string? oldValue, string? newValue)
|
||||||
|
{
|
||||||
|
if (newValue == null) return;
|
||||||
|
|
||||||
|
Color = new(GetColor(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsRequestBusy = true;
|
||||||
|
if (string.IsNullOrEmpty(RequestPath))
|
||||||
|
throw new Exception("Path is empty.");
|
||||||
|
|
||||||
|
var method = Method switch
|
||||||
|
{
|
||||||
|
"GET" => HttpMethod.Get,
|
||||||
|
"POST" => HttpMethod.Post,
|
||||||
|
"PUT" => HttpMethod.Put,
|
||||||
|
"DELETE" => HttpMethod.Delete,
|
||||||
|
"HEAD" => HttpMethod.Head,
|
||||||
|
"PATCH" => HttpMethod.Patch,
|
||||||
|
"OPTIONS" => HttpMethod.Options,
|
||||||
|
"TRACE" => HttpMethod.Trace,
|
||||||
|
_ => throw new Exception("Method is not selected or missing."),
|
||||||
|
};
|
||||||
|
|
||||||
|
var processInfo = ProcessFinder.GetProcessInfo();
|
||||||
|
RequestText?.Invoke(this, this);
|
||||||
|
var content = new StringContent(RequestBody ?? string.Empty, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||||
|
var client = Connector.GetLcuHttpClientInstance();
|
||||||
|
var response = await client.SendAsync(new(method, RequestPath) { Content = content });
|
||||||
|
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||||
|
var responseBody = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
|
||||||
|
var body = responseBody.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
|
||||||
|
if (body.Length >= App.MaxCharacters)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(body));
|
||||||
|
UpdateText?.Invoke(this, string.Empty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ResponseBody = body;
|
||||||
|
UpdateText?.Invoke(this, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode.ToString()}";
|
||||||
|
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
|
||||||
|
ResponseAuthentication = riotAuthentication.Value;
|
||||||
|
ResponseAuthorization = $"Basic {riotAuthentication.Value}";
|
||||||
|
ResponseUsername = riotAuthentication.Username;
|
||||||
|
ResponsePassword = riotAuthentication.Password;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
||||||
|
UpdateText?.Invoke(this, string.Empty);
|
||||||
|
|
||||||
|
ResponseStatus = null;
|
||||||
|
ResponsePath = null;
|
||||||
|
ResponseAuthentication = null;
|
||||||
|
ResponseAuthorization = null;
|
||||||
|
ResponseUsername = null;
|
||||||
|
ResponsePassword = null;
|
||||||
|
ResponseBody = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsRequestBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color GetColor(string method) => method switch
|
||||||
|
{
|
||||||
|
"GET" => Avalonia.Media.Color.FromRgb(95, 99, 186),
|
||||||
|
"POST" => Avalonia.Media.Color.FromRgb(103, 186, 95),
|
||||||
|
"PUT" => Avalonia.Media.Color.FromRgb(186, 139, 95),
|
||||||
|
"DELETE" => Avalonia.Media.Color.FromRgb(186, 95, 95),
|
||||||
|
"HEAD" => Avalonia.Media.Color.FromRgb(136, 95, 186),
|
||||||
|
"PATCH" => Avalonia.Media.Color.FromRgb(95, 186, 139),
|
||||||
|
_ => throw new InvalidOperationException("Method does not have assigned color.")
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Controls.Primitives;
|
|
||||||
using Avalonia.Styling;
|
|
||||||
using AvaloniaEdit;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Needlework.Net.Extensions;
|
|
||||||
using Needlework.Net.Messages;
|
|
||||||
using Needlework.Net.ViewModels;
|
|
||||||
using TextMateSharp.Grammars;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
|
||||||
|
|
||||||
public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>, IRecipient<ContentRequestMessage>
|
|
||||||
{
|
|
||||||
private TextEditor? _requestEditor;
|
|
||||||
private TextEditor? _responseEditor;
|
|
||||||
|
|
||||||
public EndpointView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate(e);
|
|
||||||
|
|
||||||
var vm = (EndpointViewModel)DataContext!;
|
|
||||||
_requestEditor = this.FindControl<TextEditor>("EndpointRequestEditor");
|
|
||||||
_responseEditor = this.FindControl<TextEditor>("EndpointResponseEditor");
|
|
||||||
_requestEditor?.ApplyJsonEditorSettings();
|
|
||||||
_responseEditor?.ApplyJsonEditorSettings();
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Register<EditorUpdateMessage>(this);
|
|
||||||
WeakReferenceMessenger.Default.Register<ContentRequestMessage, string>(this, "EndpointRequestEditor");
|
|
||||||
|
|
||||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnDetachedFromVisualTree(e);
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
|
||||||
{
|
|
||||||
var registryOptions = new RegistryOptions(
|
|
||||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(EditorUpdateMessage message)
|
|
||||||
{
|
|
||||||
switch (message.Value.Key)
|
|
||||||
{
|
|
||||||
case "EndpointRequestEditor":
|
|
||||||
_requestEditor!.Text = message.Value.Text;
|
|
||||||
break;
|
|
||||||
case "EndpointResponseEditor":
|
|
||||||
_responseEditor!.Text = message.Value.Text;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(ContentRequestMessage message)
|
|
||||||
{
|
|
||||||
message.Reply(_requestEditor!.Text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
|
||||||
|
|
||||||
public partial class EndpointsContainerView : UserControl
|
|
||||||
{
|
|
||||||
public EndpointsContainerView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Views
|
|
||||||
{
|
|
||||||
public partial class EndpointsView : UserControl
|
|
||||||
{
|
|
||||||
public EndpointsView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Views
|
|
||||||
{
|
|
||||||
public partial class HomeView : UserControl
|
|
||||||
{
|
|
||||||
public HomeView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.MainWindow"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.MainWindow"
|
x:Class="Needlework.Net.Views.MainWindow.MainWindowView"
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Title="Needlework.Net"
|
Title="Needlework.Net"
|
||||||
Icon="/Assets/app.ico"
|
Icon="/Assets/app.ico"
|
||||||
@@ -37,6 +37,8 @@
|
|||||||
<ui:NavigationView AlwaysShowHeader="False"
|
<ui:NavigationView AlwaysShowHeader="False"
|
||||||
PaneDisplayMode="Left"
|
PaneDisplayMode="Left"
|
||||||
IsSettingsVisible="False"
|
IsSettingsVisible="False"
|
||||||
|
IsPaneOpen="False"
|
||||||
|
OpenPaneLength="200"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
MenuItemsSource="{Binding MenuItems}"
|
MenuItemsSource="{Binding MenuItems}"
|
||||||
SelectedItem="{Binding SelectedMenuItem}">
|
SelectedItem="{Binding SelectedMenuItem}">
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
using FluentAvalonia.UI.Windowing;
|
using FluentAvalonia.UI.Windowing;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.MainWindow;
|
||||||
|
|
||||||
public partial class MainWindow : AppWindow
|
public partial class MainWindowView : AppWindow
|
||||||
{
|
{
|
||||||
public MainWindow()
|
public MainWindowView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
64
Needlework.Net/Views/MainWindow/OopsiesDialog.cs
Normal file
64
Needlework.Net/Views/MainWindow/OopsiesDialog.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Needlework.Net.Services;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.MainWindow;
|
||||||
|
|
||||||
|
public class OopsiesDialog : IDialog, IDisposable
|
||||||
|
{
|
||||||
|
private bool _isDisposing;
|
||||||
|
private string? _text;
|
||||||
|
private ContentDialog _dialog;
|
||||||
|
|
||||||
|
public OopsiesDialog()
|
||||||
|
{
|
||||||
|
_dialog = new ContentDialog
|
||||||
|
{
|
||||||
|
PrimaryButtonText = "Open",
|
||||||
|
CloseButtonText = "Cancel",
|
||||||
|
Title = "Oopsies",
|
||||||
|
Content = "This response is too large to handle for performance reasons.\nIt can be viewed in an external editor or viewer.",
|
||||||
|
IsPrimaryButtonEnabled = true,
|
||||||
|
IsSecondaryButtonEnabled = false,
|
||||||
|
DefaultButton = ContentDialogButton.Primary
|
||||||
|
};
|
||||||
|
_dialog.PrimaryButtonClick += OnPrimaryButtonClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ContentDialogResult> ShowAsync(object data)
|
||||||
|
{
|
||||||
|
_text = (string)data;
|
||||||
|
var result = await _dialog.ShowAsync();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
|
{
|
||||||
|
var temp = Path.GetTempFileName().Replace(".tmp", ".json");
|
||||||
|
File.WriteAllText(temp, _text);
|
||||||
|
Process.Start("explorer", "\"" + temp + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_text = null;
|
||||||
|
_dialog.PrimaryButtonClick -= OnPrimaryButtonClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using FluentAvalonia.UI.Controls;
|
|
||||||
using Needlework.Net.Services;
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Views
|
|
||||||
{
|
|
||||||
public class OopsiesDialog : IDialog, IDisposable
|
|
||||||
{
|
|
||||||
private bool _isDisposing;
|
|
||||||
private string? _text;
|
|
||||||
private ContentDialog _dialog;
|
|
||||||
|
|
||||||
public OopsiesDialog()
|
|
||||||
{
|
|
||||||
_dialog = new ContentDialog
|
|
||||||
{
|
|
||||||
PrimaryButtonText = "Open",
|
|
||||||
CloseButtonText = "Cancel",
|
|
||||||
Title = "Oopsies",
|
|
||||||
Content = "This response is too large to handle for performance reasons.\nIt can be viewed in an external editor or viewer.",
|
|
||||||
IsPrimaryButtonEnabled = true,
|
|
||||||
IsSecondaryButtonEnabled = false,
|
|
||||||
DefaultButton = ContentDialogButton.Primary
|
|
||||||
};
|
|
||||||
_dialog.PrimaryButtonClick += OnPrimaryButtonClick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ContentDialogResult> ShowAsync(object data)
|
|
||||||
{
|
|
||||||
_text = (string)data;
|
|
||||||
var result = await _dialog.ShowAsync();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
|
||||||
{
|
|
||||||
var temp = Path.GetTempFileName().Replace(".tmp", ".json");
|
|
||||||
File.WriteAllText(temp, _text);
|
|
||||||
Process.Start("explorer", "\"" + temp + "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!_isDisposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_text = null;
|
|
||||||
_dialog.PrimaryButtonClick -= OnPrimaryButtonClick;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isDisposing = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.AboutView"
|
x:Class="Needlework.Net.Views.Pages.AboutView"
|
||||||
x:DataType="vm:AboutViewModel">
|
x:DataType="vm:AboutViewModel">
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Style Selector="Button">
|
<Style Selector="Button">
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
public partial class AboutView : UserControl
|
public partial class AboutView : UserControl
|
||||||
{
|
{
|
||||||
@@ -5,10 +5,10 @@
|
|||||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.ConsoleView"
|
x:Class="Needlework.Net.Views.Pages.ConsoleView"
|
||||||
x:DataType="vm:ConsoleViewModel">
|
x:DataType="vm:ConsoleViewModel">
|
||||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||||
BusyText="Loading...">
|
BusyText="Loading...">
|
||||||
@@ -19,13 +19,13 @@
|
|||||||
<StackPanel Margin="0 0 0 16">
|
<StackPanel Margin="0 0 0 16">
|
||||||
<Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto">
|
<Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto">
|
||||||
<ComboBox ItemsSource="{Binding RequestMethods}"
|
<ComboBox ItemsSource="{Binding RequestMethods}"
|
||||||
SelectedItem="{Binding RequestMethodSelected}"
|
SelectedItem="{Binding LcuRequest.Method}"
|
||||||
Margin="0 0 8 0"
|
Margin="0 0 8 0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"/>
|
Grid.Column="0"/>
|
||||||
<AutoCompleteBox
|
<AutoCompleteBox
|
||||||
ItemsSource="{Binding RequestPaths}"
|
ItemsSource="{Binding RequestPaths}"
|
||||||
Text="{Binding RequestPath}"
|
Text="{Binding LcuRequest.RequestPath}"
|
||||||
MaxDropDownHeight="400"
|
MaxDropDownHeight="400"
|
||||||
FilterMode="StartsWith"
|
FilterMode="StartsWith"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<TextBox IsReadOnly="True"
|
<TextBox IsReadOnly="True"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Text="{Binding ResponsePath}"/>
|
Text="{Binding LcuRequest.ResponsePath}"/>
|
||||||
<avaloniaEdit:TextEditor
|
<avaloniaEdit:TextEditor
|
||||||
Name="RequestEditor"
|
Name="RequestEditor"
|
||||||
Text=""
|
Text=""
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
<Button Content="{Binding ResponseStatus}"
|
<Button Content="{Binding LcuRequest.ResponseStatus}"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using AvaloniaEdit;
|
using AvaloniaEdit;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.ViewModels.Pages;
|
||||||
using Needlework.Net.ViewModels;
|
|
||||||
using TextMateSharp.Grammars;
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessage>, IRecipient<ContentRequestMessage>
|
public partial class ConsoleView : UserControl
|
||||||
{
|
{
|
||||||
private TextEditor? _responseEditor;
|
private TextEditor? _responseEditor;
|
||||||
private TextEditor? _requestEditor;
|
private TextEditor? _requestEditor;
|
||||||
@@ -21,36 +18,39 @@ public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessag
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(ResponseUpdatedMessage message)
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
_responseEditor!.Text = message.Value;
|
base.OnAttachedToVisualTree(e);
|
||||||
}
|
|
||||||
|
|
||||||
public void Receive(ContentRequestMessage message)
|
|
||||||
{
|
|
||||||
message.Reply(_requestEditor!.Text);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate(e);
|
|
||||||
|
|
||||||
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
||||||
_requestEditor = this.FindControl<TextEditor>("RequestEditor");
|
_requestEditor = this.FindControl<TextEditor>("RequestEditor");
|
||||||
_responseEditor?.ApplyJsonEditorSettings();
|
_responseEditor?.ApplyJsonEditorSettings();
|
||||||
_requestEditor?.ApplyJsonEditorSettings();
|
_requestEditor?.ApplyJsonEditorSettings();
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Register<ResponseUpdatedMessage, string>(this, nameof(ConsoleViewModel));
|
var vm = (ConsoleViewModel)DataContext!;
|
||||||
WeakReferenceMessenger.Default.Register<ContentRequestMessage, string>(this, "ConsoleRequestEditor");
|
vm.LcuRequest.RequestText += LcuRequest_RequestText; ;
|
||||||
|
vm.LcuRequest.UpdateText += LcuRequest_UpdateText;
|
||||||
|
|
||||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LcuRequest_RequestText(object? sender, ViewModels.Shared.LcuRequestViewModel e)
|
||||||
|
{
|
||||||
|
e.RequestBody = _requestEditor!.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LcuRequest_UpdateText(object? sender, string e)
|
||||||
|
{
|
||||||
|
_responseEditor!.Text = e;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnDetachedFromVisualTree(e);
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
var vm = (ConsoleViewModel)DataContext!;
|
||||||
|
vm.LcuRequest.RequestText -= LcuRequest_RequestText;
|
||||||
|
vm.LcuRequest.UpdateText -= LcuRequest_UpdateText;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.EndpointView"
|
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointView"
|
||||||
x:DataType="vm:EndpointViewModel">
|
x:DataType="vm:EndpointViewModel">
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Style Selector="DataGrid">
|
<Style Selector="DataGrid">
|
||||||
@@ -59,8 +59,8 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
Margin="0 0 8 0"
|
Margin="0 0 8 0"
|
||||||
Text="{Binding Method}"
|
Text="{Binding LcuRequest.Value.Method}"
|
||||||
Background="{Binding Color}"
|
Background="{Binding LcuRequest.Value.Color}"
|
||||||
FontSize="8"
|
FontSize="8"
|
||||||
Width="50"
|
Width="50"
|
||||||
Padding="10 2 10 2"
|
Padding="10 2 10 2"
|
||||||
@@ -88,14 +88,14 @@
|
|||||||
ColumnDefinitions="auto,*,auto">
|
ColumnDefinitions="auto,*,auto">
|
||||||
<TextBox Grid.Row="0"
|
<TextBox Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Text="{Binding SelectedPathOperation.Method}"
|
Text="{Binding SelectedPathOperation.LcuRequest.Value.Method}"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Margin="0 0 8 0"/>
|
Margin="0 0 8 0"/>
|
||||||
<TextBox Grid.Row="0"
|
<TextBox Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Text="{Binding SelectedPathOperation.ResponsePath}"
|
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponsePath}"
|
||||||
IsReadOnly="True"/>
|
IsReadOnly="True"/>
|
||||||
<StackPanel Grid.Row="0"
|
<StackPanel Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="0 0 0 8"
|
Margin="0 0 0 8"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Text="{Binding SelectedPathOperation.ResponseUsername}" />
|
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponseUsername}" />
|
||||||
<TextBlock FontSize="12"
|
<TextBlock FontSize="12"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="0 0 0 8"
|
Margin="0 0 0 8"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Text="{Binding SelectedPathOperation.ResponsePassword}"/>
|
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponsePassword}"/>
|
||||||
<TextBlock FontSize="12"
|
<TextBlock FontSize="12"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -212,7 +212,7 @@
|
|||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Text="{Binding SelectedPathOperation.ResponseAuthorization}"/>
|
Text="{Binding SelectedPathOperation.LcuRequest.Value.ResponseAuthorization}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Schemas">
|
<TabItem Header="Schemas">
|
||||||
@@ -304,10 +304,12 @@
|
|||||||
FontSize="10"
|
FontSize="10"
|
||||||
Padding="12 4 12 4"
|
Padding="12 4 12 4"
|
||||||
Classes="Flat"
|
Classes="Flat"
|
||||||
Content="{Binding SelectedPathOperation.ResponseStatus}"/>
|
Content="{Binding SelectedPathOperation.LcuRequest.Value.ResponseStatus}"/>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Grid Grid.Row="1" Grid.Column="4">
|
<Grid Grid.Row="1" Grid.Column="4">
|
||||||
|
<controls:BusyArea BusyText="Loading..."
|
||||||
|
IsBusy="{Binding SelectedPathOperation.IsBusy}">
|
||||||
<TabControl>
|
<TabControl>
|
||||||
<TabItem Header="Preview">
|
<TabItem Header="Preview">
|
||||||
<avalonEdit:TextEditor
|
<avalonEdit:TextEditor
|
||||||
@@ -320,6 +322,7 @@
|
|||||||
FontSize="12"/>
|
FontSize="12"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
|
</controls:BusyArea>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
94
Needlework.Net/Views/Pages/Endpoints/EndpointView.axaml.cs
Normal file
94
Needlework.Net/Views/Pages/Endpoints/EndpointView.axaml.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using AvaloniaEdit;
|
||||||
|
using Needlework.Net.Extensions;
|
||||||
|
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
using Needlework.Net.ViewModels.Shared;
|
||||||
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointView : UserControl
|
||||||
|
{
|
||||||
|
private TextEditor? _requestEditor;
|
||||||
|
private TextEditor? _responseEditor;
|
||||||
|
private LcuRequestViewModel? _lcuRequestVm;
|
||||||
|
|
||||||
|
public EndpointView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnAttachedToVisualTree(e);
|
||||||
|
|
||||||
|
_requestEditor = this.FindControl<TextEditor>("EndpointRequestEditor");
|
||||||
|
_responseEditor = this.FindControl<TextEditor>("EndpointResponseEditor");
|
||||||
|
_requestEditor?.ApplyJsonEditorSettings();
|
||||||
|
_responseEditor?.ApplyJsonEditorSettings();
|
||||||
|
|
||||||
|
var vm = (EndpointViewModel)DataContext!;
|
||||||
|
vm.PathOperationSelected += Vm_PathOperationSelected;
|
||||||
|
|
||||||
|
if (vm.SelectedPathOperation != null)
|
||||||
|
{
|
||||||
|
_lcuRequestVm = vm.SelectedPathOperation.LcuRequest.Value;
|
||||||
|
vm.SelectedPathOperation.LcuRequest.Value.RequestText += LcuRequest_RequestText;
|
||||||
|
vm.SelectedPathOperation.LcuRequest.Value.UpdateText += LcuRequest_UpdateText;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Vm_PathOperationSelected(object? sender, string e)
|
||||||
|
{
|
||||||
|
var vm = (EndpointViewModel)DataContext!;
|
||||||
|
if (vm.SelectedPathOperation != null)
|
||||||
|
{
|
||||||
|
_requestEditor!.Text = e;
|
||||||
|
if (_lcuRequestVm != null)
|
||||||
|
{
|
||||||
|
_lcuRequestVm.RequestText -= LcuRequest_RequestText;
|
||||||
|
_lcuRequestVm.UpdateText -= LcuRequest_UpdateText;
|
||||||
|
}
|
||||||
|
vm.SelectedPathOperation.LcuRequest.Value.RequestText += LcuRequest_RequestText;
|
||||||
|
vm.SelectedPathOperation.LcuRequest.Value.UpdateText += LcuRequest_UpdateText;
|
||||||
|
_lcuRequestVm = vm.SelectedPathOperation.LcuRequest.Value;
|
||||||
|
_responseEditor!.Text = vm.SelectedPathOperation.LcuRequest.Value.ResponseBody ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
|
var vm = (EndpointViewModel)DataContext!;
|
||||||
|
vm.PathOperationSelected -= Vm_PathOperationSelected;
|
||||||
|
|
||||||
|
if (_lcuRequestVm != null)
|
||||||
|
{
|
||||||
|
_lcuRequestVm.RequestText -= LcuRequest_RequestText;
|
||||||
|
_lcuRequestVm.UpdateText -= LcuRequest_UpdateText;
|
||||||
|
_lcuRequestVm = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||||
|
{
|
||||||
|
var registryOptions = new RegistryOptions(
|
||||||
|
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LcuRequest_RequestText(object? sender, LcuRequestViewModel e)
|
||||||
|
{
|
||||||
|
e.RequestBody = _requestEditor!.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LcuRequest_UpdateText(object? sender, string e)
|
||||||
|
{
|
||||||
|
_responseEditor!.Text = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.EndpointsContainerView"
|
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointsNavigationView"
|
||||||
x:DataType="vm:EndpointsContainerViewModel">
|
x:DataType="vm:EndpointsNavigationViewModel">
|
||||||
<Grid RowDefinitions="auto,*"
|
<Grid RowDefinitions="auto,*"
|
||||||
ColumnDefinitions="*"
|
ColumnDefinitions="*"
|
||||||
Margin="16">
|
Margin="16">
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointsNavigationView : UserControl
|
||||||
|
{
|
||||||
|
public EndpointsNavigationView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Needlework.Net/Views/Pages/Endpoints/EndpointsTabView.axaml
Normal file
35
Needlework.Net/Views/Pages/Endpoints/EndpointsTabView.axaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||||
|
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||||
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointsTabView"
|
||||||
|
x:DataType="vm:EndpointsTabViewModel">
|
||||||
|
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||||
|
BusyText="Loading...">
|
||||||
|
<Grid>
|
||||||
|
<ui:TabView TabItems="{Binding Endpoints}"
|
||||||
|
AddTabButtonCommand="{Binding AddEndpointCommand}"
|
||||||
|
TabCloseRequested="TabView_TabCloseRequested">
|
||||||
|
<ui:TabView.TabItemTemplate>
|
||||||
|
<DataTemplate DataType="vm:EndpointItem">
|
||||||
|
<ui:TabViewItem Header="{Binding Header}"
|
||||||
|
IconSource="{Binding IconSource}"
|
||||||
|
IsSelected="{Binding Selected}"
|
||||||
|
Content="{Binding}">
|
||||||
|
<ui:TabViewItem.ContentTemplate>
|
||||||
|
<DataTemplate DataType="vm:EndpointItem">
|
||||||
|
<ContentControl Content="{Binding Content}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ui:TabViewItem.ContentTemplate>
|
||||||
|
</ui:TabViewItem>
|
||||||
|
</DataTemplate>
|
||||||
|
</ui:TabView.TabItemTemplate>
|
||||||
|
</ui:TabView>
|
||||||
|
</Grid>
|
||||||
|
</controls:BusyArea>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointsTabView : UserControl
|
||||||
|
{
|
||||||
|
public EndpointsTabView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TabView_TabCloseRequested(FluentAvalonia.UI.Controls.TabView sender, FluentAvalonia.UI.Controls.TabViewTabCloseRequestedEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Tab.Content is EndpointItem item)
|
||||||
|
((IList)sender.TabItems).Remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,14 +2,12 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Endpoints"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
Name="EndpointsControl"
|
Name="EndpointsControl"
|
||||||
x:Class="Needlework.Net.Views.EndpointsView"
|
x:Class="Needlework.Net.Views.Pages.Endpoints.EndpointsView"
|
||||||
x:DataType="vm:EndpointsViewModel">
|
x:DataType="vm:EndpointsViewModel">
|
||||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
|
||||||
BusyText="Loading...">
|
|
||||||
<Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
<Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||||
<TextBox Watermark="Search" Margin="0 4" Text="{Binding Search}" Grid.Row="1" Grid.Column="0"/>
|
<TextBox Watermark="Search" Margin="0 4" Text="{Binding Search}" Grid.Row="1" Grid.Column="0"/>
|
||||||
<ScrollViewer Grid.Row="2" Grid.Column="0">
|
<ScrollViewer Grid.Row="2" Grid.Column="0">
|
||||||
@@ -28,5 +26,4 @@
|
|||||||
</ItemsRepeater>
|
</ItemsRepeater>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</controls:BusyArea>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
11
Needlework.Net/Views/Pages/Endpoints/EndpointsView.axaml.cs
Normal file
11
Needlework.Net/Views/Pages/Endpoints/EndpointsView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.Pages.Endpoints;
|
||||||
|
|
||||||
|
public partial class EndpointsView : UserControl
|
||||||
|
{
|
||||||
|
public EndpointsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages"
|
||||||
xmlns:controls="using:Needlework.Net.Controls"
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.HomeView"
|
x:Class="Needlework.Net.Views.Pages.HomeView"
|
||||||
x:DataType="vm:HomeViewModel">
|
x:DataType="vm:HomeViewModel">
|
||||||
<!-- TOP LEVEL -->
|
<!-- TOP LEVEL -->
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
11
Needlework.Net/Views/Pages/HomeView.axaml.cs
Normal file
11
Needlework.Net/Views/Pages/HomeView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
|
public partial class HomeView : UserControl
|
||||||
|
{
|
||||||
|
public HomeView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
xmlns:vm="using:Needlework.Net.ViewModels.Pages.Websocket"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Views.WebsocketView"
|
x:Class="Needlework.Net.Views.Pages.WebsocketView"
|
||||||
x:DataType="vm:WebsocketViewModel">
|
x:DataType="vm:WebsocketViewModel">
|
||||||
<Grid RowDefinitions="*,auto,*" Margin="16">
|
<Grid RowDefinitions="*,auto,*" Margin="16">
|
||||||
<Border Grid.Row="0"
|
<Border Grid.Row="0"
|
||||||
@@ -6,11 +6,11 @@ using AvaloniaEdit;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.ViewModels;
|
using Needlework.Net.ViewModels.Pages.Websocket;
|
||||||
using System;
|
using System;
|
||||||
using TextMateSharp.Grammars;
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
namespace Needlework.Net.Views;
|
namespace Needlework.Net.Views.Pages;
|
||||||
|
|
||||||
public partial class WebsocketView : UserControl, IRecipient<ResponseUpdatedMessage>
|
public partial class WebsocketView : UserControl, IRecipient<ResponseUpdatedMessage>
|
||||||
{
|
{
|
||||||
@@ -4,7 +4,14 @@
|
|||||||
|
|
||||||
Needlework.Net is an open-source helper tool for the LCU that provides documented endpoints and can send requests without any code setup. Created using .NET! 🌠
|
Needlework.Net is an open-source helper tool for the LCU that provides documented endpoints and can send requests without any code setup. Created using .NET! 🌠
|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
- Interactive OpenAPI documentation
|
||||||
|
- REST data transfer console
|
||||||
|
- WebSocket event data viewer
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
- Windows x64
|
- Windows x64
|
||||||
- .NET 8 runtime. [It can be downloaded here.](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.7-windows-x64-installer)
|
- .NET 8 runtime. [It can be downloaded here.](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.7-windows-x64-installer)
|
||||||
|
|
||||||
@@ -21,6 +28,7 @@ Needlework.Net is an open-source helper tool for the LCU that provides documente
|
|||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
### GrrrLCU
|
### GrrrLCU
|
||||||
|
|
||||||
A simple wrapper for the LCU.
|
A simple wrapper for the LCU.
|
||||||
- [Repository](https://github.com/BlossomiShymae/GrrrLCU)
|
- [Repository](https://github.com/BlossomiShymae/GrrrLCU)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user