Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
375285067d | ||
|
|
3ec277bdd3 | ||
|
|
c097890588 | ||
|
|
3352740733 | ||
|
|
48751efc28 | ||
|
|
b6f713c675 | ||
|
|
59619764c2 | ||
|
|
de6f9f64dd | ||
|
|
4eae0bd913 | ||
|
|
7288c471a4 | ||
|
|
7faedcf039 | ||
|
|
641d230647 | ||
|
|
d53c24c57f | ||
|
|
a24a72b3b2 | ||
|
|
2c88ae44a2 | ||
|
|
f0294b3042 | ||
|
|
d26180dce5 | ||
|
|
baf189e6a9 | ||
|
|
88149d1458 | ||
|
|
79fd79c01d | ||
|
|
7550102406 | ||
|
|
98996609a3 | ||
|
|
65464d22e3 | ||
|
|
0ca7f7869d | ||
|
|
af47e7c763 | ||
|
|
04058f12c1 | ||
|
|
3a7d39971a | ||
|
|
b0b5476c48 | ||
|
|
b3158a81b8 | ||
|
|
83400bceed | ||
|
|
1133f2d785 | ||
|
|
14dde760b0 | ||
|
|
e0a2685dcf | ||
|
|
ca2f8c4852 | ||
|
|
8f81aa526e | ||
|
|
360a0f28c7 |
6
.github/workflows/release.yml
vendored
@@ -20,14 +20,14 @@ jobs:
|
||||
fetch-depth: 0
|
||||
ref: release
|
||||
- name: Build
|
||||
run: dotnet build Needlework.Net.Desktop -c Release
|
||||
run: dotnet build Needlework.Net -c Release
|
||||
- name: Publish
|
||||
run: dotnet publish Needlework.Net.Desktop -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 Needlework.Net -c Release -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -p:DebugSymbols=false -o publish -r win-x64 --self-contained=false
|
||||
- name: Get Version
|
||||
id: version
|
||||
shell: powershell
|
||||
run: |
|
||||
$xml=[xml](Get-Content .\Needlework.Net.Desktop\Needlework.Net.Desktop.csproj)
|
||||
$xml=[xml](Get-Content .\Needlework.Net\Needlework.Net.csproj)
|
||||
$ver=($xml.Project.PropertyGroup).AssemblyVersion
|
||||
$ver="VERSION=$ver"
|
||||
$ver=$ver -replace '\s',''
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Needlework.Net.Core.Tests;
|
||||
|
||||
public class LcuSchemaHandlerTest
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
internal HttpClient HttpClient { get; } = new();
|
||||
|
||||
public LcuSchemaHandlerTest(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PluginsTestAsync()
|
||||
{
|
||||
var reader = new LcuSchemaHandler(await Resources.GetOpenApiDocumentAsync(HttpClient));
|
||||
|
||||
var plugins = reader.Plugins.Keys.ToList();
|
||||
foreach (var plugin in plugins)
|
||||
_output.WriteLine($"Plugin: {plugin}");
|
||||
|
||||
Assert.True(plugins.Count > 0);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Needlework.Net.Core\Needlework.Net.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,23 +0,0 @@
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Needlework.Net.Core.Tests;
|
||||
|
||||
public class ResourcesTest
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
internal HttpClient HttpClient { get; } = new();
|
||||
|
||||
public ResourcesTest(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DocumentTestAsync()
|
||||
{
|
||||
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
||||
|
||||
Assert.True(document.Info.Title == "LCU SCHEMA");
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="1.6.16" />
|
||||
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.16" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,9 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Core;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class DataReadyMessage(LcuSchemaHandler handler) : ValueChangedMessage<LcuSchemaHandler>(handler)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Core;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class DataRequestMessage : RequestMessage<LcuSchemaHandler>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class OopsiesWindowCanceledMessage(object? data) : ValueChangedMessage<object?>(data)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class OopsiesWindowRequestedMessage(string text) : ValueChangedMessage<string>(text)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using Needlework.Net.Desktop.ViewModels;
|
||||
using Needlework.Net.Desktop.Views;
|
||||
using SukiUI.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Needlework.Net.Desktop.Services
|
||||
{
|
||||
public class WindowService : IRecipient<OopsiesWindowCanceledMessage>
|
||||
{
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public Dictionary<string, SukiWindow> EndpointWindows { get; } = []; // Workaround memory leak by storing and reusing windows.
|
||||
// Figure out why creating and closing windows leaks memory.
|
||||
|
||||
public OopsiesWindow? OopsiesWindow { get; set; }
|
||||
|
||||
public WindowService(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
|
||||
WeakReferenceMessenger.Default.Register<OopsiesWindowCanceledMessage>(this);
|
||||
}
|
||||
|
||||
public void ShowOopsiesWindow(string text)
|
||||
{
|
||||
if (OopsiesWindow != null) OopsiesWindow!.Close();
|
||||
|
||||
var window = new OopsiesWindow();
|
||||
window.DataContext = new OopsiesWindowViewModel(text);
|
||||
window.Show(App.MainWindow!);
|
||||
window.Closed += OnOopsiesWindowClosed;
|
||||
OopsiesWindow = window;
|
||||
}
|
||||
|
||||
public void OnOopsiesWindowClosed(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender == null) return;
|
||||
|
||||
var window = (OopsiesWindow)sender;
|
||||
window.DataContext = null;
|
||||
window.Closed -= OnOopsiesWindowClosed;
|
||||
OopsiesWindow = null;
|
||||
}
|
||||
|
||||
public void Receive(OopsiesWindowCanceledMessage message)
|
||||
{
|
||||
if (OopsiesWindow is OopsiesWindow window) window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.Desktop
|
||||
{
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
public Control? Build(object? param)
|
||||
{
|
||||
if (param is null) return new TextBlock { Text = "data was 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public class AboutViewModel : PageBase
|
||||
{
|
||||
public AboutViewModel() : base("About", Material.Icons.MaterialIconKind.InfoCircle)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using SukiUI.Controls;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class EndpointsContainerViewModel : PageBase
|
||||
{
|
||||
[ObservableProperty] private ISukiStackPageTitleProvider _activeViewModel;
|
||||
|
||||
public EndpointsContainerViewModel(HttpClient httpClient) : base("Endpoints", Material.Icons.MaterialIconKind.Hub, -500)
|
||||
{
|
||||
_activeViewModel = new EndpointsViewModel(httpClient, OnClicked);
|
||||
}
|
||||
|
||||
private void OnClicked(ISukiStackPageTitleProvider viewModel)
|
||||
{
|
||||
ActiveViewModel = viewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
using Avalonia.Media;
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class HomeViewModel : PageBase
|
||||
{
|
||||
[ObservableProperty] private string _statusText = string.Empty;
|
||||
[ObservableProperty] private IBrush? _statusForeground;
|
||||
[ObservableProperty] private string _statusAddress = string.Empty;
|
||||
|
||||
public HomeViewModel() : base("Home", Material.Icons.MaterialIconKind.Home, int.MinValue)
|
||||
{
|
||||
var thread = new Thread(StartProcessing) { IsBackground = true };
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
private void StartProcessing()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
void Set(string text, Color color, string address)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
StatusText = text;
|
||||
StatusForeground = new SolidColorBrush(color.ToUInt32());
|
||||
StatusAddress = address;
|
||||
});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var processInfo = Connector.GetProcessInfo();
|
||||
Set("Online", Colors.Green, $"https://127.0.0.1:{processInfo.AppPort}/");
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
Set("Offline", Colors.Red, "N/A");
|
||||
}
|
||||
|
||||
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(url)
|
||||
{
|
||||
UseShellExecute = true
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Core;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using Needlework.Net.Desktop.Services;
|
||||
using SukiUI.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class MainWindowViewModel : ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<OopsiesWindowRequestedMessage>
|
||||
{
|
||||
public IAvaloniaReadOnlyList<PageBase> Pages { get; }
|
||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||
|
||||
public HttpClient HttpClient { get; }
|
||||
public WindowService WindowService { get; }
|
||||
public LcuSchemaHandler? LcuSchemaHandler { get; set; }
|
||||
public OpenApiDocument? HostDocument { get; set; }
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
|
||||
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, WindowService windowService)
|
||||
{
|
||||
Pages = new AvaloniaList<PageBase>(pages.OrderBy(x => x.Index).ThenBy(x => x.DisplayName));
|
||||
HttpClient = httpClient;
|
||||
WindowService = windowService;
|
||||
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
Task.Run(FetchDataAsync);
|
||||
}
|
||||
|
||||
private async Task FetchDataAsync()
|
||||
{
|
||||
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
||||
HostDocument = document;
|
||||
var handler = new LcuSchemaHandler(document);
|
||||
LcuSchemaHandler = handler;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
||||
await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () => await SukiHost.ShowToast("OpenAPI Data Processed", "Some pages can now be used.", SukiUI.Enums.NotificationType.Success, TimeSpan.FromSeconds(5)));
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
public void Receive(DataRequestMessage message)
|
||||
{
|
||||
message.Reply(LcuSchemaHandler!);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenConsole()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Receive(OopsiesWindowRequestedMessage message)
|
||||
{
|
||||
WindowService.ShowOopsiesWindow(message.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class OopsiesWindowViewModel(string text) : ObservableObject
|
||||
{
|
||||
public string Text { get; } = text;
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenDefaultEditor()
|
||||
{
|
||||
var temp = Path.GetTempFileName().Replace(".tmp", ".json");
|
||||
File.WriteAllText(temp, Text);
|
||||
Process.Start("explorer", "\"" + temp + "\"");
|
||||
CloseDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CloseDialog()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new OopsiesWindowCanceledMessage(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Material.Icons;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
|
||||
public abstract partial class PageBase(string displayName, MaterialIconKind icon, int index = 0) : ObservableValidator
|
||||
{
|
||||
[ObservableProperty] private string _displayName = displayName;
|
||||
[ObservableProperty] private MaterialIconKind _icon = icon;
|
||||
[ObservableProperty] private int _index = index;
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
using Avalonia.Media;
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Core;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using SukiUI.Controls;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Desktop.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(pathOperation.Method.ToUpper()));
|
||||
Path = pathOperation.Path;
|
||||
Operation = new OperationViewModel(pathOperation.Operation);
|
||||
ProcessInfo = GetProcessInfo();
|
||||
ResponsePath = ProcessInfo != null ? $"https://127.0.0.1:{ProcessInfo.AppPort}{Path}" : null;
|
||||
ResponseUsername = ProcessInfo != null ? new RiotAuthentication(ProcessInfo.RemotingAuthToken).Username : null;
|
||||
ResponsePassword = ProcessInfo != null ? new RiotAuthentication(ProcessInfo.RemotingAuthToken).Password : null;
|
||||
ResponseAuthorization = ProcessInfo != null ? $"Basic {new RiotAuthentication(ProcessInfo.RemotingAuthToken).Value}" : null;
|
||||
}
|
||||
|
||||
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.ToUpper() 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 path = Path;
|
||||
foreach (var pathParameter in Operation.PathParameters)
|
||||
{
|
||||
path = path.Replace($"{{{pathParameter.Name}}}", pathParameter.Value);
|
||||
}
|
||||
|
||||
var query = "";
|
||||
foreach (var queryParameter in Operation.QueryParameters)
|
||||
{
|
||||
if (query.Length != 0 && !string.IsNullOrWhiteSpace(queryParameter.Value))
|
||||
query += $"&{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}";
|
||||
else if (query.Length == 0 && !string.IsNullOrWhiteSpace(queryParameter.Value))
|
||||
query += $"?{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}";
|
||||
}
|
||||
var uri = $"{path}{query}";
|
||||
|
||||
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "EndpointRequestEditor").Response;
|
||||
var content = new StringContent(Regex.Replace(requestBody, @"\s+", ""), new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
|
||||
var response = await Connector.SendAsync(method, $"{uri}", content) ?? throw new Exception("Response is null.");
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
responseBody = !string.IsNullOrEmpty(responseBody) ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
|
||||
if (responseBody.Length >= App.MaxCharacters) WeakReferenceMessenger.Default.Send(new OopsiesWindowRequestedMessage(responseBody));
|
||||
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)
|
||||
{
|
||||
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
|
||||
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,41 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.AboutView"
|
||||
x:DataType="vm:AboutViewModel">
|
||||
<Grid Margin="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<WrapPanel
|
||||
theme:WrapPanelExtensions.AnimatedScroll="true"
|
||||
Orientation="Horizontal">
|
||||
<suki:GlassCard Margin="8">
|
||||
<Image Source="/Assets/about.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="200"
|
||||
Height="200"/>
|
||||
</suki:GlassCard>
|
||||
<StackPanel>
|
||||
<suki:GlassCard Width="400" Margin="8">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h3">Blossomi Shymae</TextBlock>
|
||||
</StackPanel>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Width="400" Margin="8">
|
||||
<suki:GroupBox Header="About">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Needlework.Net is .NET rewrite of Needlework. Like Needlework, this project is inspired by
|
||||
LCU Explorer. This tool was made to help others with LCU development. Feel free to ask any questions
|
||||
or help contribute to the project! 💜
|
||||
</TextBlock>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,86 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.ConsoleView"
|
||||
x:DataType="vm:ConsoleViewModel">
|
||||
<suki:BusyArea IsBusy="{Binding IsBusy}" BusyText="Loading...">
|
||||
<Grid Margin="16" RowDefinitions="auto,*" ColumnDefinitions="*,*">
|
||||
<Grid Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2">
|
||||
<suki:GlassCard Margin="0 0 0 16">
|
||||
<suki:GroupBox Header="Console">
|
||||
<Grid RowDefinitions="auto,auto" ColumnDefinitions="auto,*">
|
||||
<ComboBox ItemsSource="{Binding RequestMethods}" SelectedItem="{Binding RequestMethodSelected}"
|
||||
Grid.Row="0" Grid.Column="0"/>
|
||||
<AutoCompleteBox
|
||||
ItemsSource="{Binding RequestPaths}"
|
||||
Text="{Binding RequestPath}"
|
||||
MaxDropDownHeight="400"
|
||||
FilterMode="StartsWith"
|
||||
Grid.Row="0" Grid.Column="1"/>
|
||||
<avaloniaEdit:TextEditor
|
||||
Name="RequestEditor"
|
||||
Text=""
|
||||
ShowLineNumbers="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
FontSize="12"
|
||||
Height="100"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"/>
|
||||
</Grid>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
<Button Classes="Flat Rounded"
|
||||
Margin="0 0 0 0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
FontWeight="DemiBold"
|
||||
Command="{Binding SendRequestCommand}"
|
||||
theme:ButtonExtensions.ShowProgress="{Binding IsRequestBusy}">
|
||||
Send
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel
|
||||
Margin="0 0 8 0"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0">
|
||||
<suki:GlassCard Margin="0 4">
|
||||
<suki:GroupBox Header="Path">
|
||||
<TextBlock Text="{Binding ResponsePath}"/>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="0 4">
|
||||
<suki:GroupBox Header="Status">
|
||||
<TextBlock Text="{Binding ResponseStatus}"/>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="0 4">
|
||||
<suki:GroupBox Header="Authorization">
|
||||
<TextBlock Text="{Binding ResponseAuthorization}" />
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
</StackPanel>
|
||||
<suki:GlassCard
|
||||
Margin="0 8"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1">
|
||||
<avaloniaEdit:TextEditor
|
||||
Name="ResponseEditor"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
ShowLineNumbers="True"
|
||||
Text=""
|
||||
FontSize="12"/>
|
||||
</suki:GlassCard>
|
||||
</Grid>
|
||||
</suki:BusyArea>
|
||||
</UserControl>
|
||||
@@ -1,14 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.EndpointsContainerView"
|
||||
x:DataType="vm:EndpointsContainerViewModel">
|
||||
<suki:SukiStackPage Content="{Binding ActiveViewModel}"
|
||||
Margin="-24 -4 0 0"/>
|
||||
</UserControl>
|
||||
@@ -1,25 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.EndpointsView"
|
||||
x:DataType="vm:EndpointsViewModel">
|
||||
<suki:BusyArea IsBusy="{Binding IsBusy}" BusyText="Loading...">
|
||||
<Grid Margin="16" RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||
<TextBox Watermark="Search" Margin="0 4" Text="{Binding Search}" Grid.Row="1" Grid.Column="0"/>
|
||||
<ScrollViewer Grid.Row="2" Grid.Column="0">
|
||||
<ListBox ItemsSource="{Binding Query}" SelectedItem="{Binding SelectedQuery}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" Foreground="White" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</suki:BusyArea>
|
||||
</UserControl>
|
||||
@@ -1,77 +0,0 @@
|
||||
<suki:SukiWindow
|
||||
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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Title="Needlework.Net"
|
||||
Icon="/Assets/app.ico"
|
||||
Width="1280"
|
||||
Height="720">
|
||||
<suki:SukiWindow.LogoContent>
|
||||
<Image Source="/Assets/app.png"
|
||||
Width="20"
|
||||
Height="20"
|
||||
VerticalAlignment="Center"/>
|
||||
</suki:SukiWindow.LogoContent>
|
||||
<!-- TOP LEVEL -->
|
||||
<suki:SukiSideMenu ItemsSource="{Binding Pages}">
|
||||
<!-- ITEMS -->
|
||||
<suki:SukiSideMenu.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<suki:SukiSideMenuItem Header="{Binding DisplayName}">
|
||||
<suki:SukiSideMenuItem.Icon>
|
||||
<materialIcons:MaterialIcon Kind="{Binding Icon}" />
|
||||
</suki:SukiSideMenuItem.Icon>
|
||||
</suki:SukiSideMenuItem>
|
||||
</DataTemplate>
|
||||
</suki:SukiSideMenu.ItemTemplate>
|
||||
<!-- FOOTER -->
|
||||
<suki:SukiSideMenu.FooterContent>
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<StackPanel.Styles>
|
||||
<Style Selector="Button.Basic">
|
||||
<Setter Property="Command" Value="{Binding OpenUrlCommand}" />
|
||||
</Style>
|
||||
<Style Selector="materialIcons|MaterialIcon">
|
||||
<Setter Property="Width" Value="25" />
|
||||
<Setter Property="Height" Value="25" />
|
||||
</Style>
|
||||
<Style Selector="i|Icon">
|
||||
<Setter Property="FontSize" Value="25" />
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
<Button Classes="Flat"
|
||||
Content="{Binding Version}"
|
||||
FontSize="12"
|
||||
Margin="0 0 4 0"
|
||||
Padding="12 4 12 4"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button Classes="Basic"
|
||||
VerticalAlignment="Center"
|
||||
CommandParameter="https://github.com/BlossomiShymae/Needlework.Net"
|
||||
ToolTip.Tip="Open on GitHub."
|
||||
Margin="0 0 4 0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialIcons:MaterialIcon Kind="Github" Margin="0 0 4 0" />
|
||||
<TextBlock FontSize="12"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="White">Star</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="Basic"
|
||||
VerticalAlignment="Center"
|
||||
CommandParameter="https://discord.gg/chEvEX5J4E"
|
||||
ToolTip.Tip="Open Discord server.">
|
||||
<i:Icon Value="fa-brand fa-discord" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</suki:SukiSideMenu.FooterContent>
|
||||
</suki:SukiSideMenu>
|
||||
</suki:SukiWindow>
|
||||
@@ -1,11 +0,0 @@
|
||||
using SukiUI.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
|
||||
public partial class MainWindow : SukiWindow
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<suki:SukiWindow
|
||||
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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.OopsiesWindow"
|
||||
x:DataType="vm:OopsiesWindowViewModel"
|
||||
Title="Needlework.Net - Oopsies"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Width="560"
|
||||
Height="200">
|
||||
<Grid RowDefinitions="auto,auto,auto" ColumnDefinitions="auto,auto"
|
||||
Margin="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2">
|
||||
This response is too large for Needlework.Net to handle for performance reasons.
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0 0 0 12">
|
||||
It can be viewed in an external editor or viewer.
|
||||
</TextBlock>
|
||||
<Button Command="{Binding OpenDefaultEditorCommand}"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0 0 8 0">
|
||||
Open
|
||||
</Button>
|
||||
<Button Command="{Binding CloseDialogCommand}"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="8 0 0 0">
|
||||
Cancel
|
||||
</Button>
|
||||
</Grid>
|
||||
</suki:SukiWindow>
|
||||
@@ -1,11 +0,0 @@
|
||||
using SukiUI.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
|
||||
public partial class OopsiesWindow : SukiWindow
|
||||
{
|
||||
public OopsiesWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.WebsocketView"
|
||||
x:DataType="vm:WebsocketViewModel">
|
||||
<Grid RowDefinitions="*,2,*" Margin="16">
|
||||
<Border Grid.Row="0"
|
||||
Padding="0 0 0 8">
|
||||
<suki:GlassCard>
|
||||
<Grid RowDefinitions="auto,*" ColumnDefinitions="*">
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="auto,*,auto,auto">
|
||||
<Button Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Command="{Binding ClearCommand}"
|
||||
Margin="0 0 8 0">Clear</Button>
|
||||
<TextBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0 0 8 0"
|
||||
Text="{Binding Search, Mode=TwoWay}"/>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Margin="0 0 8 0">
|
||||
<ToggleSwitch Margin="0 0 0 8"
|
||||
IsChecked="{Binding IsAttach}"/>
|
||||
<TextBlock Margin="0 6 0 0"
|
||||
FontSize="18">Attach</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="0"
|
||||
Grid.Column="3">
|
||||
<ToggleSwitch Margin="0 0 0 8"
|
||||
IsChecked="{Binding IsTail}"/>
|
||||
<TextBlock Margin="0 6 0 0"
|
||||
FontSize="18">Tail</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<ListBox Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Name="EventViewer"
|
||||
ItemsSource="{Binding FilteredEventLog}"
|
||||
SelectedItem="{Binding SelectedEventLog}"/>
|
||||
</Grid>
|
||||
</suki:GlassCard>
|
||||
</Border>
|
||||
<GridSplitter Grid.Row="1" ResizeDirection="Rows" Background="Gray"/>
|
||||
<Border Grid.Row="2"
|
||||
Padding="0 8 0 0">
|
||||
<suki:GlassCard>
|
||||
<avaloniaEdit:TextEditor
|
||||
Name="ResponseEditor"
|
||||
ShowLineNumbers="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
Text=""
|
||||
FontSize="12"/>
|
||||
</suki:GlassCard>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -3,11 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Needlework.Net.Core", "Needlework.Net.Core\Needlework.Net.Core.csproj", "{B14E1B39-3C5A-400F-8148-CC3A4833CBC4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Needlework.Net.Desktop", "Needlework.Net.Desktop\Needlework.Net.Desktop.csproj", "{7388B579-2DC0-46D6-957A-6683D0FCF5D3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Needlework.Net.Core.Tests", "Needlework.Net.Core.Tests\Needlework.Net.Core.Tests.csproj", "{0E08542E-6E3F-4825-9F9C-7D6275D6AEC5}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Needlework.Net", "Needlework.Net\Needlework.Net.csproj", "{7388B579-2DC0-46D6-957A-6683D0FCF5D3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -18,17 +14,9 @@ Global
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B14E1B39-3C5A-400F-8148-CC3A4833CBC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B14E1B39-3C5A-400F-8148-CC3A4833CBC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B14E1B39-3C5A-400F-8148-CC3A4833CBC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B14E1B39-3C5A-400F-8148-CC3A4833CBC4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7388B579-2DC0-46D6-957A-6683D0FCF5D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7388B579-2DC0-46D6-957A-6683D0FCF5D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7388B579-2DC0-46D6-957A-6683D0FCF5D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7388B579-2DC0-46D6-957A-6683D0FCF5D3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0E08542E-6E3F-4825-9F9C-7D6275D6AEC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E08542E-6E3F-4825-9F9C-7D6275D6AEC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E08542E-6E3F-4825-9F9C-7D6275D6AEC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0E08542E-6E3F-4825-9F9C-7D6275D6AEC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Needlework.Net.Desktop.App"
|
||||
RequestedThemeVariant="Dark"
|
||||
xmlns:local="using:Needlework.Net.Desktop"
|
||||
xmlns:converters="using:Needlework.Net.Desktop.Converters"
|
||||
xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
|
||||
x:Class="Needlework.Net.App"
|
||||
xmlns:local="using:Needlework.Net"
|
||||
xmlns:converters="using:Needlework.Net.Converters"
|
||||
xmlns:sty="using:FluentAvalonia.Styling"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
RequestedThemeVariant="Dark">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme></FluentTheme>
|
||||
<sukiUi:SukiTheme ThemeColor="Blue" />
|
||||
<sty:FluentAvaloniaTheme PreferSystemTheme="False" PreferUserAccentColor="False" />
|
||||
<materialIcons:MaterialIconStyles />
|
||||
<StyleInclude Source="Controls/Card.axaml"/>
|
||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
</Application.Styles>
|
||||
@@ -3,12 +3,12 @@ using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Needlework.Net.Desktop.ViewModels;
|
||||
using Needlework.Net.Desktop.Views;
|
||||
using Needlework.Net.ViewModels;
|
||||
using Needlework.Net.Views;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net.Desktop;
|
||||
namespace Needlework.Net;
|
||||
|
||||
public partial class App(IServiceProvider serviceProvider) : Application
|
||||
{
|
||||
BIN
Needlework.Net/Assets/Icons/home.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Needlework.Net/Assets/Icons/info-circle.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Needlework.Net/Assets/Icons/list-alt.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Needlework.Net/Assets/Icons/plug.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Needlework.Net/Assets/Icons/terminal.png
Normal file
|
After Width: | Height: | Size: 938 B |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
BIN
Needlework.Net/Assets/Users/community.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
Needlework.Net/Assets/Users/dubble.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
Needlework.Net/Assets/Users/dysolix.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
Needlework.Net/Assets/Users/ray.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
66
Needlework.Net/Controls/BusyArea.axaml
Normal file
@@ -0,0 +1,66 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Controls.BusyArea">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="controls|BusyArea">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Panel>
|
||||
<ContentControl Content="{TemplateBinding Content}"/>
|
||||
<DockPanel Name="LoadingBusyArea"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
LastChildFill="True">
|
||||
<TextBlock Margin="16"
|
||||
DockPanel.Dock="Bottom"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="DemiBold"
|
||||
Text="{TemplateBinding BusyText}"/>
|
||||
<ProgressBar
|
||||
Width="100"
|
||||
IsIndeterminate="True"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</DockPanel>
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|BusyArea DockPanel#LoadingBusyArea">
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.3" />
|
||||
</Transitions>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|BusyArea[IsBusy=True] DockPanel#LoadingBusyArea">
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|BusyArea[IsBusy=False] DockPanel#LoadingBusyArea">
|
||||
<Setter Property="Opacity" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|BusyArea ContentControl">
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.3"/>
|
||||
</Transitions>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|BusyArea[IsBusy=True] ContentControl">
|
||||
<Setter Property="Opacity" Value="0.1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|BusyArea[IsBusy=False] ContentControl">
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
</UserControl>
|
||||
30
Needlework.Net/Controls/BusyArea.axaml.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Controls;
|
||||
|
||||
public partial class BusyArea : UserControl
|
||||
{
|
||||
public BusyArea()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsBusyProperty =
|
||||
AvaloniaProperty.Register<BusyArea, bool>(nameof(IsBusy), defaultValue: false);
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get { return GetValue(IsBusyProperty); }
|
||||
set { SetValue(IsBusyProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string?> BusyTextProperty =
|
||||
AvaloniaProperty.Register<BusyArea, string?>(nameof(BusyText), defaultValue: null);
|
||||
|
||||
public string? BusyText
|
||||
{
|
||||
get => GetValue(BusyTextProperty);
|
||||
set => SetValue(BusyTextProperty, value);
|
||||
}
|
||||
}
|
||||
20
Needlework.Net/Controls/Card.axaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Needlework.Net.Controls">
|
||||
<Design.PreviewWith>
|
||||
<controls:Card />
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Style Selector="controls|Card">
|
||||
<!-- Set Defaults -->
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border Padding="16"
|
||||
CornerRadius="16,16,16,16"
|
||||
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}">
|
||||
<ContentPresenter Content="{TemplateBinding Content}"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
10
Needlework.Net/Controls/Card.axaml.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Controls;
|
||||
|
||||
public class Card : ContentControl
|
||||
{
|
||||
public Card()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Desktop.Converters
|
||||
namespace Needlework.Net.Converters
|
||||
{
|
||||
public class EnumerableBoolConverter : IValueConverter
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Needlework.Net.Desktop.Converters
|
||||
namespace Needlework.Net.Converters
|
||||
{
|
||||
public class NullBoolConverter : IValueConverter
|
||||
{
|
||||
22
Needlework.Net/Extensions/ServiceCollectionExtensions.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Needlework.Net.Extensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddSingletonsFromAssemblies<T>(this ServiceCollection services)
|
||||
{
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => !p.IsAbstract && typeof(T).IsAssignableFrom(p));
|
||||
|
||||
foreach (var type in types) services.AddSingleton(typeof(T), type);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using AvaloniaEdit;
|
||||
using AvaloniaEdit.Highlighting;
|
||||
using AvaloniaEdit.Indentation.CSharp;
|
||||
|
||||
namespace Needlework.Net.Desktop.Extensions
|
||||
namespace Needlework.Net.Extensions
|
||||
{
|
||||
public static class TextEditorExtensions
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class ContentRequestMessage : RequestMessage<string>
|
||||
{
|
||||
9
Needlework.Net/Messages/DataReadyMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Models;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class DataReadyMessage(OpenApiDocumentWrapper wrapper) : ValueChangedMessage<OpenApiDocumentWrapper>(wrapper)
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Needlework.Net/Messages/DataRequestMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Models;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class DataRequestMessage : RequestMessage<OpenApiDocumentWrapper>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class EditorUpdateMessage(EditorUpdate editorUpdate) : ValueChangedMessage<EditorUpdate>(editorUpdate)
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class HostDocumentRequestMessage : RequestMessage<OpenApiDocument>
|
||||
{
|
||||
9
Needlework.Net/Messages/InfoBarUpdateMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.ViewModels;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class InfoBarUpdateMessage(InfoBarViewModel vm) : ValueChangedMessage<InfoBarViewModel>(vm)
|
||||
{
|
||||
}
|
||||
}
|
||||
8
Needlework.Net/Messages/OopsiesDialogRequestedMessage.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class OopsiesDialogRequestedMessage(string text) : ValueChangedMessage<string>(text)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
namespace Needlework.Net.Messages
|
||||
{
|
||||
public class ResponseUpdatedMessage(string data) : ValueChangedMessage<string>(data)
|
||||
{
|
||||
12
Needlework.Net/Models/GithubRelease.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Needlework.Net.Models
|
||||
{
|
||||
public class GithubRelease
|
||||
{
|
||||
[JsonPropertyName("tag_name")]
|
||||
public string TagName { get; set; } = string.Empty;
|
||||
|
||||
public bool IsLatest(int version) => int.Parse(TagName.Replace(".", "")) > version;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Core;
|
||||
namespace Needlework.Net.Models;
|
||||
|
||||
public class LcuSchemaHandler
|
||||
public class OpenApiDocumentWrapper
|
||||
{
|
||||
internal OpenApiDocument OpenApiDocument { get; }
|
||||
|
||||
@@ -12,7 +13,7 @@ public class LcuSchemaHandler
|
||||
|
||||
public List<string> Paths => [.. OpenApiDocument.Paths.Keys];
|
||||
|
||||
public LcuSchemaHandler(OpenApiDocument openApiDocument)
|
||||
public OpenApiDocumentWrapper(OpenApiDocument openApiDocument)
|
||||
{
|
||||
OpenApiDocument = openApiDocument;
|
||||
var plugins = new SortedDictionary<string, List<PathOperation>>();
|
||||
@@ -68,5 +69,3 @@ public class LcuSchemaHandler
|
||||
Plugins = plugins;
|
||||
}
|
||||
}
|
||||
|
||||
public record PathOperation(string Method, string Path, OpenApiOperation Operation);
|
||||
5
Needlework.Net/Models/PathOperation.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Models;
|
||||
|
||||
public record PathOperation(string Method, string Path, OpenApiOperation Operation);
|
||||
@@ -1,7 +1,9 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi.Readers;
|
||||
|
||||
namespace Needlework.Net.Core;
|
||||
namespace Needlework.Net.Models;
|
||||
|
||||
public static class Resources
|
||||
{
|
||||
@@ -11,39 +11,37 @@
|
||||
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
<AssemblyName>NeedleworkDotNet</AssemblyName>
|
||||
<AssemblyVersion>0.2.2.0</AssemblyVersion>
|
||||
<FileVersion>0.2.2.0</FileVersion>
|
||||
<AssemblyVersion>0.7.0.0</AssemblyVersion>
|
||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.3" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.0-beta2" />
|
||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
|
||||
<PackageReference Include="BlossomiShymae.GrrrLCU" Version="0.9.0" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
|
||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" />
|
||||
<PackageReference Include="BlossomiShymae.GrrrLCU" Version="0.13.1" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="1.6.17" />
|
||||
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.17" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia" Version="9.4.0" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.0" />
|
||||
<PackageReference Include="SukiUI" Version="6.0.0-beta7" />
|
||||
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.60" />
|
||||
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.62" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Needlework.Net.Core\Needlework.Net.Core.csproj" />
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -51,15 +49,16 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Controls\BusyArea.axaml.cs">
|
||||
<DependentUpon>BusyArea.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\EndpointView.axaml.cs">
|
||||
<DependentUpon>EndpointView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Views\OopsiesWindow.axaml.cs">
|
||||
<DependentUpon>OopsiesWindow.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\Users\" />
|
||||
<Folder Include="Utilities\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,13 +1,14 @@
|
||||
using Avalonia;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Needlework.Net.Desktop.Services;
|
||||
using Needlework.Net.Desktop.ViewModels;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.ViewModels;
|
||||
using Projektanker.Icons.Avalonia;
|
||||
using Projektanker.Icons.Avalonia.FontAwesome;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
|
||||
namespace Needlework.Net.Desktop;
|
||||
namespace Needlework.Net;
|
||||
|
||||
class Program
|
||||
{
|
||||
@@ -15,8 +16,13 @@ class Program
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += Program_UnhandledException;
|
||||
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
@@ -35,17 +41,17 @@ class Program
|
||||
var builder = new ServiceCollection();
|
||||
|
||||
builder.AddSingleton<MainWindowViewModel>();
|
||||
builder.AddSingleton<WindowService>();
|
||||
// Dynamically add ViewModels
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => !p.IsAbstract && typeof(PageBase).IsAssignableFrom(p));
|
||||
foreach (var type in types)
|
||||
builder.AddSingleton(typeof(PageBase), type);
|
||||
builder.AddSingleton<DialogService>();
|
||||
builder.AddSingletonsFromAssemblies<PageBase>();
|
||||
|
||||
builder.AddHttpClient();
|
||||
|
||||
var services = builder.BuildServiceProvider();
|
||||
return services;
|
||||
}
|
||||
|
||||
private static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
File.WriteAllText($"errorlog-{DateTime.Now:HHmmssfff}", e.ExceptionObject.ToString());
|
||||
}
|
||||
}
|
||||
20
Needlework.Net/Services/DialogService.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Services
|
||||
{
|
||||
public class DialogService
|
||||
{
|
||||
public async Task<ContentDialogResult> ShowAsync<T>(object data)
|
||||
where T : IDialog, IDisposable
|
||||
{
|
||||
T dialog = Activator.CreateInstance<T>();
|
||||
|
||||
var result = await dialog.ShowAsync(data);
|
||||
dialog.Dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Needlework.Net/Services/IDialog.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Services
|
||||
{
|
||||
public interface IDialog
|
||||
{
|
||||
public Task<ContentDialogResult> ShowAsync(object data);
|
||||
}
|
||||
}
|
||||
40
Needlework.Net/ViewLocator.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Needlework.Net
|
||||
{
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
private readonly Dictionary<object, Control> _controlCache = [];
|
||||
|
||||
public Control Build(object? data)
|
||||
{
|
||||
var fullName = data?.GetType().FullName;
|
||||
if (fullName is null)
|
||||
{
|
||||
return new TextBlock { Text = "Data is null or has no name." };
|
||||
}
|
||||
|
||||
var name = fullName.Replace("ViewModel", "View");
|
||||
var type = Type.GetType(name);
|
||||
if (type is null)
|
||||
{
|
||||
return new TextBlock { Text = $"No View For {name}." };
|
||||
}
|
||||
|
||||
if (!_controlCache.TryGetValue(data!, out var res))
|
||||
{
|
||||
res ??= (Control)Activator.CreateInstance(type)!;
|
||||
_controlCache[data!] = res;
|
||||
}
|
||||
|
||||
res.DataContext = data;
|
||||
return res;
|
||||
}
|
||||
|
||||
public bool Match(object? data) => data is INotifyPropertyChanged;
|
||||
}
|
||||
}
|
||||
26
Needlework.Net/ViewModels/AboutViewModel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class AboutViewModel : PageBase
|
||||
{
|
||||
public HttpClient HttpClient { get; }
|
||||
|
||||
public AboutViewModel(HttpClient httpClient) : base("About", "info-circle")
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,24 +3,21 @@ using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using Needlework.Net.Desktop.Services;
|
||||
using SukiUI.Controls;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
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 IAvaloniaReadOnlyList<string> _requestPaths = new AvaloniaList<string>();
|
||||
[ObservableProperty] private string? _requestMethodSelected = "GET";
|
||||
[ObservableProperty] private string? _requestPath = null;
|
||||
[ObservableProperty] private string? _requestBody = null;
|
||||
@@ -28,12 +25,8 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
[ObservableProperty] private string? _responseStatus = null;
|
||||
[ObservableProperty] private string? _responseAuthorization = null;
|
||||
|
||||
public WindowService WindowService { get; }
|
||||
|
||||
public ConsoleViewModel(WindowService windowService) : base("Console", Material.Icons.MaterialIconKind.Console, -200)
|
||||
public ConsoleViewModel() : base("Console", "terminal", -200)
|
||||
{
|
||||
WindowService = windowService;
|
||||
|
||||
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
||||
}
|
||||
|
||||
@@ -58,15 +51,20 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
_ => throw new Exception("Method is not selected."),
|
||||
};
|
||||
|
||||
var processInfo = Connector.GetProcessInfo();
|
||||
var processInfo = ProcessFinder.Get();
|
||||
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "ConsoleRequestEditor").Response;
|
||||
var content = new StringContent(Regex.Replace(requestBody, @"\s+", ""), new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
var response = await Connector.SendAsync(method, RequestPath, content) ?? throw new Exception("Response is null.");
|
||||
var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
var client = Connector.GetLcuHttpClientInstance();
|
||||
var response = await client.SendAsync(new(method, RequestPath) { Content = content });
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var responseBody = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
body = !string.IsNullOrEmpty(body) ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(body), App.JsonSerializerOptions) : string.Empty;
|
||||
if (body.Length >= App.MaxCharacters) WindowService.ShowOopsiesWindow(body);
|
||||
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()}";
|
||||
@@ -75,7 +73,7 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
|
||||
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;
|
||||
@@ -91,7 +89,8 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
RequestPaths = new AvaloniaList<string>([.. message.Value.Paths]);
|
||||
RequestPaths.Clear();
|
||||
RequestPaths.AddRange(message.Value.Paths);
|
||||
IsBusy = false;
|
||||
});
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using SukiUI.Controls;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class EndpointViewModel : ObservableObject, ISukiStackPageTitleProvider
|
||||
public partial class EndpointViewModel : ObservableObject
|
||||
{
|
||||
public string Endpoint { get; }
|
||||
public string Title => Endpoint;
|
||||
|
||||
|
||||
[ObservableProperty] private IAvaloniaReadOnlyList<PathOperationViewModel> _pathOperations;
|
||||
public IAvaloniaReadOnlyList<PathOperationViewModel> PathOperations { get; }
|
||||
[ObservableProperty] private PathOperationViewModel? _selectedPathOperation;
|
||||
|
||||
[ObservableProperty] private string? _search;
|
||||
[ObservableProperty] private IAvaloniaReadOnlyList<PathOperationViewModel> _filteredPathOperations;
|
||||
public IAvaloniaList<PathOperationViewModel> FilteredPathOperations { get; }
|
||||
|
||||
public EndpointViewModel(string endpoint)
|
||||
{
|
||||
@@ -30,13 +30,20 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
|
||||
partial void OnSearchChanged(string? value)
|
||||
{
|
||||
FilteredPathOperations.Clear();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations);
|
||||
FilteredPathOperations.AddRange(PathOperations);
|
||||
return;
|
||||
}
|
||||
FilteredPathOperations.AddRange(PathOperations.Where(o => o.Path.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
||||
}
|
||||
|
||||
FilteredPathOperations = new AvaloniaList<PathOperationViewModel>(PathOperations.Where(o => o.Path.ToLower().Contains(value.ToLower())));
|
||||
partial void OnSelectedPathOperationChanged(PathOperationViewModel? value)
|
||||
{
|
||||
if (value == null) return;
|
||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(value.Operation.RequestTemplate ?? string.Empty, "EndpointRequestEditor")));
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Needlework.Net/ViewModels/EndpointsContainerViewModel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class EndpointsContainerViewModel : PageBase
|
||||
{
|
||||
[ObservableProperty] private ObservableObject _activeViewModel;
|
||||
[ObservableProperty] private ObservableObject _endpointsViewModel;
|
||||
[ObservableProperty] private string _title = string.Empty;
|
||||
|
||||
public EndpointsContainerViewModel(HttpClient httpClient) : base("Endpoints", "list-alt", -500)
|
||||
{
|
||||
_activeViewModel = _endpointsViewModel = new EndpointsViewModel(httpClient, OnClicked);
|
||||
}
|
||||
|
||||
private void OnClicked(ObservableObject viewModel)
|
||||
{
|
||||
ActiveViewModel = viewModel;
|
||||
if (viewModel is EndpointViewModel endpoint) Title = endpoint.Title;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void GoBack()
|
||||
{
|
||||
ActiveViewModel = EndpointsViewModel;
|
||||
Title = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,28 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using SukiUI.Controls;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class EndpointsViewModel : ObservableObject, IRecipient<DataReadyMessage>, ISukiStackPageTitleProvider
|
||||
public partial class EndpointsViewModel : ObservableObject, IRecipient<DataReadyMessage>
|
||||
{
|
||||
public HttpClient HttpClient { get; }
|
||||
|
||||
public string Title => "Endpoints";
|
||||
public Action<ISukiStackPageTitleProvider> OnClicked;
|
||||
public Action<ObservableObject> OnClicked;
|
||||
public IAvaloniaList<string> Plugins { get; } = new AvaloniaList<string>();
|
||||
public IAvaloniaList<string> Query { get; } = new AvaloniaList<string>();
|
||||
|
||||
[ObservableProperty] private IAvaloniaReadOnlyList<string> _plugins = new AvaloniaList<string>();
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
[ObservableProperty] private string _search = string.Empty;
|
||||
[ObservableProperty] private IAvaloniaReadOnlyList<string> _query = new AvaloniaList<string>();
|
||||
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
||||
|
||||
public EndpointsViewModel(HttpClient httpClient, Action<ISukiStackPageTitleProvider> onClicked)
|
||||
public EndpointsViewModel(HttpClient httpClient, Action<ObservableObject> onClicked)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
OnClicked = onClicked;
|
||||
@@ -33,19 +33,23 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
public void Receive(DataReadyMessage message)
|
||||
{
|
||||
IsBusy = false;
|
||||
Plugins = new AvaloniaList<string>([.. message.Value.Plugins.Keys]);
|
||||
Query = new AvaloniaList<string>([.. Plugins]);
|
||||
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 = new AvaloniaList<string>(Plugins.Where(x => x.Contains(value)));
|
||||
Query.AddRange(Plugins.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
|
||||
else
|
||||
Query = Plugins;
|
||||
Query.AddRange(Plugins);
|
||||
}
|
||||
|
||||
partial void OnSelectedQueryChanged(string? value)
|
||||
[RelayCommand]
|
||||
private void OpenEndpoint(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return;
|
||||
|
||||
22
Needlework.Net/ViewModels/EventViewModel.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public class EventViewModel : ObservableObject
|
||||
{
|
||||
public string Time { get; }
|
||||
public string Type { get; }
|
||||
public string Uri { get; }
|
||||
|
||||
public string Key => $"{Time} {Type} {Uri}";
|
||||
|
||||
public EventViewModel(EventData eventData)
|
||||
{
|
||||
Time = $"{DateTime.Now:HH:mm:ss.fff}";
|
||||
Type = eventData?.EventType.ToUpper() ?? string.Empty;
|
||||
Uri = eventData?.Uri ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Needlework.Net/ViewModels/HomeViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class HomeViewModel : PageBase
|
||||
{
|
||||
public HomeViewModel() : base("Home", "home", int.MinValue) { }
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(url) { UseShellExecute = true }
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Needlework.Net/ViewModels/InfoBarViewModel.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class InfoBarViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _title;
|
||||
[ObservableProperty] private bool _isOpen;
|
||||
[ObservableProperty] private string _message;
|
||||
[ObservableProperty] private InfoBarSeverity _severity;
|
||||
[ObservableProperty] private TimeSpan _duration;
|
||||
[ObservableProperty] private Control? _actionButton;
|
||||
|
||||
public InfoBarViewModel(string title, bool isOpen, string message, InfoBarSeverity severity, TimeSpan duration, Control? actionButton = null)
|
||||
{
|
||||
_title = title;
|
||||
_isOpen = isOpen;
|
||||
_message = message;
|
||||
_severity = severity;
|
||||
_duration = duration;
|
||||
_actionButton = actionButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
157
Needlework.Net/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
using Needlework.Net.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class MainWindowViewModel
|
||||
: ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>, IRecipient<InfoBarUpdateMessage>, IRecipient<OopsiesDialogRequestedMessage>
|
||||
{
|
||||
public IAvaloniaReadOnlyList<NavigationViewItem> MenuItems { get; }
|
||||
[NotifyPropertyChangedFor(nameof(CurrentPage))]
|
||||
[ObservableProperty] private NavigationViewItem _selectedMenuItem;
|
||||
public PageBase CurrentPage => (PageBase)SelectedMenuItem.Tag!;
|
||||
|
||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||
[ObservableProperty] private bool _isUpdateShown = false;
|
||||
|
||||
public HttpClient HttpClient { get; }
|
||||
public DialogService DialogService { get; }
|
||||
public OpenApiDocumentWrapper? OpenApiDocumentWrapper { get; set; }
|
||||
public OpenApiDocument? HostDocument { get; set; }
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
|
||||
[ObservableProperty] private ObservableCollection<InfoBarViewModel> _infoBarItems = [];
|
||||
|
||||
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient, DialogService dialogService)
|
||||
{
|
||||
MenuItems = new AvaloniaList<NavigationViewItem>(pages
|
||||
.OrderBy(p => p.Index)
|
||||
.ThenBy(p => p.DisplayName)
|
||||
.Select(p => new NavigationViewItem()
|
||||
{
|
||||
Content = p.DisplayName,
|
||||
Tag = p,
|
||||
IconSource = new BitmapIconSource() { UriSource = new Uri($"avares://NeedleworkDotNet/Assets/Icons/{p.Icon}.png") }
|
||||
}));
|
||||
SelectedMenuItem = MenuItems[0];
|
||||
|
||||
HttpClient = httpClient;
|
||||
DialogService = dialogService;
|
||||
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
|
||||
Task.Run(FetchDataAsync);
|
||||
new Thread(ProcessEvents) { IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
private void ProcessEvents(object? obj)
|
||||
{
|
||||
while (!IsUpdateShown)
|
||||
{
|
||||
Task.Run(CheckLatestVersionAsync);
|
||||
|
||||
Thread.Sleep(TimeSpan.FromSeconds(60));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckLatestVersionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/BlossomiShymae/Needlework.Net/releases/latest");
|
||||
request.Headers.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("Needlework.Net", Version));
|
||||
|
||||
var response = await HttpClient.SendAsync(request);
|
||||
var release = await response.Content.ReadFromJsonAsync<GithubRelease>();
|
||||
if (release == null) return;
|
||||
|
||||
var currentVersion = int.Parse(Version.Replace(".", ""));
|
||||
|
||||
if (release.IsLatest(currentVersion))
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ShowInfoBarAsync(new("Needlework.Net Update", true, $"There is a new version available: {release.TagName}.", InfoBarSeverity.Informational, TimeSpan.FromSeconds(10), new Avalonia.Controls.Button()
|
||||
{
|
||||
Command = OpenUrlCommand,
|
||||
CommandParameter = "https://github.com/BlossomiShymae/Needlework.Net/releases",
|
||||
Content = "Download"
|
||||
}));
|
||||
IsUpdateShown = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
private async Task FetchDataAsync()
|
||||
{
|
||||
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
||||
HostDocument = document;
|
||||
var handler = new OpenApiDocumentWrapper(document);
|
||||
OpenApiDocumentWrapper = handler;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
public void Receive(DataRequestMessage message)
|
||||
{
|
||||
message.Reply(OpenApiDocumentWrapper!);
|
||||
}
|
||||
|
||||
public void Receive(HostDocumentRequestMessage message)
|
||||
{
|
||||
message.Reply(HostDocument!);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(url)
|
||||
{
|
||||
UseShellExecute = true
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
}
|
||||
|
||||
public void Receive(InfoBarUpdateMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(async () => await ShowInfoBarAsync(message.Value));
|
||||
}
|
||||
|
||||
private async Task ShowInfoBarAsync(InfoBarViewModel vm)
|
||||
{
|
||||
InfoBarItems.Add(vm);
|
||||
await Task.Delay(vm.Duration);
|
||||
InfoBarItems.Remove(vm);
|
||||
}
|
||||
|
||||
public void Receive(OopsiesDialogRequestedMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () => await DialogService.ShowAsync<OopsiesDialog>(message.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using Needlework.Net.Messages;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class OperationViewModel : ObservableObject
|
||||
{
|
||||
@@ -18,6 +20,7 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
public IAvaloniaReadOnlyList<PropertyClassViewModel> ResponseClasses { get; }
|
||||
public IAvaloniaReadOnlyList<ParameterViewModel> PathParameters { get; }
|
||||
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
|
||||
public string? RequestTemplate { get; }
|
||||
|
||||
public OperationViewModel(OpenApiOperation operation)
|
||||
{
|
||||
@@ -30,6 +33,80 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
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)
|
||||
@@ -38,6 +115,7 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
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;
|
||||
@@ -77,6 +155,8 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
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)
|
||||
12
Needlework.Net/ViewModels/PageBase.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
|
||||
public abstract partial class PageBase(string displayName, string icon, int index = 0) : ObservableValidator
|
||||
{
|
||||
[ObservableProperty] private string _displayName = displayName;
|
||||
[ObservableProperty] private string _icon = icon;
|
||||
[ObservableProperty] private int _index = index;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class ParameterViewModel : ObservableObject
|
||||
{
|
||||
121
Needlework.Net/ViewModels/PathOperationViewModel.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Avalonia.Media;
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.Models;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class PathOperationViewModel : ObservableObject
|
||||
{
|
||||
public string Method { get; }
|
||||
public SolidColorBrush Color { get; }
|
||||
public string Path { get; }
|
||||
public OperationViewModel Operation { get; }
|
||||
|
||||
public ProcessInfo? ProcessInfo { get; }
|
||||
|
||||
[ObservableProperty] private bool _isBusy;
|
||||
|
||||
[ObservableProperty] private Lazy<ResponseViewModel> _response;
|
||||
|
||||
public PathOperationViewModel(PathOperation pathOperation)
|
||||
{
|
||||
Method = pathOperation.Method.ToUpper();
|
||||
Color = new SolidColorBrush(GetColor(Method));
|
||||
Path = pathOperation.Path;
|
||||
Operation = new OperationViewModel(pathOperation.Operation);
|
||||
Response = new(() => new ResponseViewModel(pathOperation.Path));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task SendRequest()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
var method = Method switch
|
||||
{
|
||||
"GET" => HttpMethod.Get,
|
||||
"POST" => HttpMethod.Post,
|
||||
"PUT" => HttpMethod.Put,
|
||||
"DELETE" => HttpMethod.Delete,
|
||||
"HEAD" => HttpMethod.Head,
|
||||
"PATCH" => HttpMethod.Patch,
|
||||
"OPTIONS" => HttpMethod.Options,
|
||||
"TRACE" => HttpMethod.Trace,
|
||||
_ => throw new Exception("Method is missing.")
|
||||
};
|
||||
|
||||
var processInfo = ProcessFinder.Get();
|
||||
var sb = new StringBuilder(Path);
|
||||
foreach (var pathParameter in Operation.PathParameters)
|
||||
{
|
||||
sb.Replace($"{{{pathParameter.Name}}}", pathParameter.Value);
|
||||
}
|
||||
|
||||
var firstQueryAdded = false;
|
||||
foreach (var queryParameter in Operation.QueryParameters)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(queryParameter.Value))
|
||||
{
|
||||
sb.Append(firstQueryAdded ? '&' : '?');
|
||||
firstQueryAdded = true;
|
||||
sb.Append($"{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}");
|
||||
}
|
||||
}
|
||||
var uri = sb.ToString();
|
||||
|
||||
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "EndpointRequestEditor").Response;
|
||||
var content = new StringContent(requestBody, new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||
|
||||
var client = Connector.GetLcuHttpClientInstance();
|
||||
var response = await client.SendAsync(new(method, uri) { Content = content });
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
var responseBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
var responseBody = responseBytes.Length > 0 ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBytes), App.JsonSerializerOptions) : string.Empty;
|
||||
if (responseBody.Length >= App.MaxCharacters)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new OopsiesDialogRequestedMessage(responseBody));
|
||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(string.Empty, "EndpointResponseEditor")));
|
||||
}
|
||||
else WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(responseBody, "EndpointResponseEditor")));
|
||||
|
||||
Response.Value.Status = $"{(int)response.StatusCode} {response.StatusCode}";
|
||||
Response.Value.Path = $"https://127.0.0.1:{processInfo.AppPort}{uri}";
|
||||
Response.Value.Authentication = Response.Value.Authorization = $"Basic {riotAuthentication.Value}";
|
||||
Response.Value.Username = riotAuthentication.Username;
|
||||
Response.Value.Password = riotAuthentication.Password;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new InfoBarUpdateMessage(new InfoBarViewModel("Request Failed", true, ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error, TimeSpan.FromSeconds(5))));
|
||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(string.Empty, "EndpointResponseEditor")));
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Color GetColor(string method) => method switch
|
||||
{
|
||||
"GET" => Avalonia.Media.Color.FromRgb(95, 99, 186),
|
||||
"POST" => Avalonia.Media.Color.FromRgb(103, 186, 95),
|
||||
"PUT" => Avalonia.Media.Color.FromRgb(186, 139, 95),
|
||||
"DELETE" => Avalonia.Media.Color.FromRgb(186, 95, 95),
|
||||
"HEAD" => Avalonia.Media.Color.FromRgb(136, 95, 186),
|
||||
"PATCH" => Avalonia.Media.Color.FromRgb(95, 186, 139),
|
||||
_ => throw new InvalidOperationException("Method does not have assigned color.")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using Microsoft.OpenApi.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public class PropertyClassViewModel : ObservableObject
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public class PropertyEnumViewModel
|
||||
{
|
||||
@@ -11,7 +11,7 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
|
||||
public PropertyEnumViewModel(IList<IOpenApiAny> enumValue)
|
||||
{
|
||||
Values = $"[{string.Join(", ", enumValue.Select(x => ((OpenApiString)x).Value).ToList())}]";
|
||||
Values = $"[{string.Join(", ", enumValue.Select(x => $"\"{((OpenApiString)x).Value}\"").ToList())}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public class PropertyFieldViewModel
|
||||
{
|
||||
35
Needlework.Net/ViewModels/ResponseViewModel.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class ResponseViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string? _path;
|
||||
[ObservableProperty] private string? _status;
|
||||
[ObservableProperty] private string? _authentication;
|
||||
[ObservableProperty] private string? _username;
|
||||
[ObservableProperty] private string? _password;
|
||||
[ObservableProperty] private string? _authorization;
|
||||
|
||||
public ResponseViewModel(string path)
|
||||
{
|
||||
Path = path;
|
||||
var processInfo = GetProcessInfo();
|
||||
if (processInfo != null)
|
||||
{
|
||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
Path = $"https://127.0.0.1:{processInfo.AppPort}{path}";
|
||||
Username = riotAuthentication.Username;
|
||||
Password = riotAuthentication.Password;
|
||||
Authorization = $"Basic {riotAuthentication.RawValue}";
|
||||
}
|
||||
}
|
||||
|
||||
private static ProcessInfo? GetProcessInfo()
|
||||
{
|
||||
if (ProcessFinder.IsActive()) return ProcessFinder.Get();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Material.Icons;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using Needlework.Net.Desktop.Services;
|
||||
using Needlework.Net.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -14,30 +12,28 @@ using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Websocket.Client;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
namespace Needlework.Net.ViewModels
|
||||
{
|
||||
public partial class WebsocketViewModel : PageBase
|
||||
{
|
||||
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
||||
[ObservableProperty] private ObservableCollection<string> _eventLog = [];
|
||||
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 string? _selectedEventLog = null;
|
||||
[ObservableProperty] private EventViewModel? _selectedEventLog = null;
|
||||
|
||||
private Dictionary<string, EventMessage> _events = [];
|
||||
|
||||
public WebsocketClient? Client { get; set; }
|
||||
|
||||
public WindowService WindowService { get; }
|
||||
public IReadOnlyList<EventViewModel> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? EventLog : [.. EventLog.Where(x => x.Key.Contains(Search, StringComparison.InvariantCultureIgnoreCase))];
|
||||
|
||||
public List<string> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? [.. EventLog] : [.. EventLog.Where(x => x.ToLower().Contains(Search.ToLower()))];
|
||||
|
||||
public WebsocketViewModel(WindowService windowService) : base("Event Viewer", MaterialIconKind.Connection, -100)
|
||||
public WebsocketViewModel() : base("Event Viewer", "plug", -100)
|
||||
{
|
||||
WindowService = windowService;
|
||||
|
||||
EventLog.CollectionChanged += (s, e) => OnPropertyChanged(nameof(FilteredEventLog));
|
||||
var thread = new Thread(InitializeWebsocket) { IsBackground = true };
|
||||
thread.Start();
|
||||
}
|
||||
@@ -63,21 +59,22 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
EventLog = [];
|
||||
}
|
||||
|
||||
partial void OnSelectedEventLogChanged(string? value)
|
||||
{
|
||||
if (value == null) return;
|
||||
if (_events.TryGetValue(value, out var message))
|
||||
{
|
||||
var text = JsonSerializer.Serialize(message, App.JsonSerializerOptions);
|
||||
if (text.Length >= App.MaxCharacters) WindowService.ShowOopsiesWindow(text);
|
||||
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(text), nameof(WebsocketViewModel));
|
||||
}
|
||||
_events.Clear();
|
||||
EventLog.Clear();
|
||||
}
|
||||
|
||||
private void OnReconnection(ReconnectionInfo info)
|
||||
@@ -95,30 +92,34 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
|
||||
private void OnMessage(EventMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(async () =>
|
||||
{
|
||||
if (!IsAttach) return;
|
||||
|
||||
var line = $"{DateTime.Now:HH:mm:ss.fff} {message.Data?.EventType.ToUpper()} {message.Data?.Uri}";
|
||||
var log = EventLog.ToList();
|
||||
Trace.WriteLine($"Message: {line}");
|
||||
if (log.Count < 1000)
|
||||
{
|
||||
log.Add(line);
|
||||
_events[line] = message;
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = $"{log[0]}";
|
||||
log.RemoveAt(0);
|
||||
_events.Remove(key);
|
||||
var line = new EventViewModel(message.Data!);
|
||||
|
||||
log.Add(line);
|
||||
_events[line] = message;
|
||||
}
|
||||
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 = []; // This is a hack needed to update for ListBox
|
||||
EventLog = new ObservableCollection<string>(log);
|
||||
EventLog.Add(line);
|
||||
_events[line.Key] = message;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EventLogLock.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
167
Needlework.Net/Views/AboutView.axaml
Normal file
@@ -0,0 +1,167 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.AboutView"
|
||||
x:DataType="vm:AboutViewModel">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Theme" Value="{StaticResource TransparentButton}"/>
|
||||
<Setter Property="Command" Value="{Binding OpenUrlCommand}"/>
|
||||
</Style>
|
||||
<Style Selector="i|Icon">
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Grid Margin="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Spacing="8">
|
||||
<Grid HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:Card Margin="8">
|
||||
<Image Source="/Assets/Users/blossomishymae.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="200"
|
||||
Height="200"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="8 0 0 0">
|
||||
<controls:Card Width="400" Margin="8">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
||||
Margin="0 0 8 0">Blossomi Shymae</TextBlock>
|
||||
<Button CommandParameter="https://github.com/BlossomiShymae">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="400" Margin="8">
|
||||
<StackPanel >
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">About</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Needlework.Net is the .NET rewrite of Needlework. This tool was made to help others with LCU development. Feel free to ask any questions
|
||||
or help contribute to the project! Made with love. 💜
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Width="800">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Thanks to the friends and people who made this tool possible...</TextBlock>
|
||||
</Border>
|
||||
<WrapPanel Orientation="Horizontal">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8">
|
||||
<controls:Card>
|
||||
<Image Source="/Assets/Users/dysolix.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="100"
|
||||
Height="100"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="2 0 0 0">
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Margin="0 0 8 0">dysolix</TextBlock>
|
||||
<Button CommandParameter="https://github.com/dysolix">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel >
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
For providing and hosting an auto-generated OpenAPI document of the LCU.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8">
|
||||
<controls:Card>
|
||||
<Image Source="/Assets/Users/ray.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="100"
|
||||
Height="100"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="2 0 0 0">
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Margin="0 0 8 0">Ray</TextBlock>
|
||||
<Button CommandParameter="https://github.com/Hi-Ray">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel >
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
For guidance, advice, or providing help via HextechDocs.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8">
|
||||
<controls:Card>
|
||||
<Image Source="/Assets/Users/dubble.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="100"
|
||||
Height="100"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="4 0 0 0">
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Margin="0 0 8 0">dubble</TextBlock>
|
||||
<Button CommandParameter="https://github.com/cuppachino">
|
||||
<i:Icon Value="fa-github"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel >
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
For encouraging me to publish Needlework. This project may never have seen the light of day without him.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8">
|
||||
<controls:Card>
|
||||
<Image Source="/Assets/Users/community.png"
|
||||
RenderOptions.BitmapInterpolationMode="MediumQuality"
|
||||
Width="100"
|
||||
Height="100"/>
|
||||
</controls:Card>
|
||||
<StackPanel Margin="4 0 0 0">
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Width="250"
|
||||
TextWrapping="Wrap">Third Party Developer Community</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Width="250" Margin="2">
|
||||
<StackPanel >
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
For providing numerous documentation on the LCU.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,6 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
namespace Needlework.Net.Views;
|
||||
|
||||
public partial class AboutView : UserControl
|
||||
{
|
||||
89
Needlework.Net/Views/ConsoleView.axaml
Normal file
@@ -0,0 +1,89 @@
|
||||
<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:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.ConsoleView"
|
||||
x:DataType="vm:ConsoleViewModel">
|
||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||
BusyText="Loading...">
|
||||
<Grid Margin="16" RowDefinitions="auto,*" ColumnDefinitions="*,*">
|
||||
<Grid Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2">
|
||||
<StackPanel Margin="0 0 0 16">
|
||||
<Grid RowDefinitions="auto" ColumnDefinitions="auto,*,auto">
|
||||
<ComboBox ItemsSource="{Binding RequestMethods}"
|
||||
SelectedItem="{Binding RequestMethodSelected}"
|
||||
Margin="0 0 8 0"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"/>
|
||||
<AutoCompleteBox
|
||||
ItemsSource="{Binding RequestPaths}"
|
||||
Text="{Binding RequestPath}"
|
||||
MaxDropDownHeight="400"
|
||||
FilterMode="StartsWith"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"/>
|
||||
<Button Margin="8 0 0 0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
FontWeight="DemiBold"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Command="{Binding SendRequestCommand}">
|
||||
Send
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
RowDefinitions="auto,*"
|
||||
ColumnDefinitions="*">
|
||||
<TextBox IsReadOnly="True"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="{Binding ResponsePath}"/>
|
||||
<avaloniaEdit:TextEditor
|
||||
Name="RequestEditor"
|
||||
Text=""
|
||||
ShowLineNumbers="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
Margin="0 8 0 0"
|
||||
FontSize="12"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"/>
|
||||
</Grid>
|
||||
<Grid RowDefinitions="35,*"
|
||||
ColumnDefinitions="*"
|
||||
Margin="8 0 0 0"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0">
|
||||
<Button Content="{Binding ResponseStatus}"
|
||||
FontSize="12"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<avaloniaEdit:TextEditor
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Name="ResponseEditor"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
ShowLineNumbers="True"
|
||||
IsReadOnly="True"
|
||||
Text=""
|
||||
FontSize="12"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</controls:BusyArea>
|
||||
</UserControl>
|
||||
@@ -1,17 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Styling;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.TextMate;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Extensions;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using Needlework.Net.Desktop.ViewModels;
|
||||
using SukiUI;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.ViewModels;
|
||||
using TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
namespace Needlework.Net.Views;
|
||||
|
||||
public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessage>, IRecipient<ContentRequestMessage>
|
||||
{
|
||||
@@ -33,9 +30,9 @@ public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessag
|
||||
message.Reply(_requestEditor!.Text);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
base.OnAttachedToVisualTree(e);
|
||||
|
||||
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
||||
_requestEditor = this.FindControl<TextEditor>("RequestEditor");
|
||||
@@ -46,7 +43,6 @@ public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessag
|
||||
WeakReferenceMessenger.Default.Register<ContentRequestMessage, string>(this, "ConsoleRequestEditor");
|
||||
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
@@ -54,19 +50,11 @@ public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessag
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
SukiTheme.GetInstance().OnBaseThemeChanged -= OnBaseThemeChanged;
|
||||
}
|
||||
|
||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||
{
|
||||
var registryOptions = new RegistryOptions(
|
||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||
|
||||
var responseTmi = _responseEditor.InstallTextMate(registryOptions);
|
||||
responseTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||
.GetLanguageByExtension(".json").Id));
|
||||
var requestTmi = _requestEditor.InstallTextMate(registryOptions);
|
||||
requestTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||
.GetLanguageByExtension(".json").Id));
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,35 @@
|
||||
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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.EndpointView"
|
||||
x:Class="Needlework.Net.Views.EndpointView"
|
||||
x:DataType="vm:EndpointViewModel">
|
||||
<Grid RowDefinitions="auto,*" ColumnDefinitions="3*,2,4*,2,4*">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="DataGrid">
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource ControlElevationBorderBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="DataGridColumnHeader TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="DataGridRow DataGridCell">
|
||||
<Setter Property="FontSize" Value="12"></Setter>
|
||||
</Style>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="Margin" Value="0 0 0 4"></Setter>
|
||||
</Style>
|
||||
<Style Selector="TabItem > TextBlock">
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontWeight" Value="DemiBold"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox ListBoxItem">
|
||||
<Setter Property="MinHeight" Value="30"/>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Grid RowDefinitions="auto,*" ColumnDefinitions="3*,auto,4*,auto,4*">
|
||||
<Grid Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
RowDefinitions="*"
|
||||
@@ -35,14 +55,14 @@
|
||||
<Grid
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="auto,*">
|
||||
<Button
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Classes="Flat"
|
||||
TextAlignment="Center"
|
||||
Margin="0 0 8 0"
|
||||
Content="{Binding Method}"
|
||||
Text="{Binding Method}"
|
||||
Background="{Binding Color}"
|
||||
FontSize="8"
|
||||
Width="45"
|
||||
Width="50"
|
||||
Padding="10 2 10 2"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"/>
|
||||
@@ -58,6 +78,7 @@
|
||||
</ListBox>
|
||||
</Grid>
|
||||
<GridSplitter Background="Gray"
|
||||
Margin="8 0 8 0"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"/>
|
||||
@@ -69,11 +90,12 @@
|
||||
Grid.Column="0"
|
||||
Text="{Binding SelectedPathOperation.Method}"
|
||||
FontSize="12"
|
||||
IsReadOnly="True"/>
|
||||
IsReadOnly="True"
|
||||
Margin="0 0 8 0"/>
|
||||
<TextBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
FontSize="12"
|
||||
Text="{Binding SelectedPathOperation.ResponsePath}"
|
||||
Text="{Binding SelectedPathOperation.Response.Value.Path}"
|
||||
IsReadOnly="True"/>
|
||||
<StackPanel Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
@@ -92,58 +114,56 @@
|
||||
<TabItem Header="Params">
|
||||
<ScrollViewer>
|
||||
<StackPanel IsVisible="{Binding SelectedPathOperation, Converter={StaticResource NullBoolConverter}}">
|
||||
<suki:GroupBox Header="Path Parameters"
|
||||
<controls:Card
|
||||
Margin="0 4"
|
||||
IsVisible="{Binding SelectedPathOperation.Operation.PathParameters, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14"
|
||||
FontWeight="DemiBold">Path Parameters</TextBlock>
|
||||
<DataGrid
|
||||
ItemsSource="{Binding SelectedPathOperation.Operation.PathParameters}"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow DataGridCell">
|
||||
<Setter Property="FontSize" Value="12"></Setter>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
|
||||
<DataGridCheckBoxColumn Header="Required" Binding="{Binding IsRequired}"/>
|
||||
<DataGridTemplateColumn Header="Value">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="vm:ParameterViewModel">
|
||||
<TextBox Text="{Binding Value}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</suki:GroupBox>
|
||||
<suki:GroupBox Header="Query Parameters"
|
||||
GridLinesVisibility="All">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
|
||||
<DataGridCheckBoxColumn Header="Required" Binding="{Binding IsRequired}"/>
|
||||
<DataGridTemplateColumn Header="Value">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="vm:ParameterViewModel">
|
||||
<TextBox Text="{Binding Value}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card
|
||||
Margin="0 4"
|
||||
IsVisible="{Binding SelectedPathOperation.Operation.QueryParameters, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14"
|
||||
FontWeight="DemiBold">Query Parameters</TextBlock>
|
||||
<DataGrid
|
||||
ItemsSource="{Binding SelectedPathOperation.Operation.QueryParameters}"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow DataGridCell">
|
||||
<Setter Property="FontSize" Value="12"></Setter>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
|
||||
<DataGridCheckBoxColumn Header="Required" Binding="{Binding IsRequired}"/>
|
||||
<DataGridTemplateColumn Header="Value">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="vm:ParameterViewModel">
|
||||
<TextBox Text="{Binding Value}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</suki:GroupBox>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
|
||||
<DataGridCheckBoxColumn Header="Required" Binding="{Binding IsRequired}"/>
|
||||
<DataGridTemplateColumn Header="Value">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="vm:ParameterViewModel">
|
||||
<TextBox Text="{Binding Value}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@@ -167,8 +187,9 @@
|
||||
<TextBox FontSize="12"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0 0 0 8"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.ResponseUsername}" />
|
||||
Text="{Binding SelectedPathOperation.Response.Value.Username}" />
|
||||
<TextBlock FontSize="12"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
@@ -178,8 +199,9 @@
|
||||
<TextBox FontSize="12"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0 0 0 8"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.ResponsePassword}"/>
|
||||
Text="{Binding SelectedPathOperation.Response.Value.Password}"/>
|
||||
<TextBlock FontSize="12"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
@@ -190,110 +212,91 @@
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.ResponseAuthorization}"/>
|
||||
Text="{Binding SelectedPathOperation.Response.Value.Authorization}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Schemas">
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<suki:GlassCard Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.RequestBodyType, Converter={StaticResource NullBoolConverter}}">
|
||||
<controls:Card Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.RequestBodyType, Converter={StaticResource NullBoolConverter}}">
|
||||
<TextBlock>
|
||||
<Run Text="Request body: " FontWeight="DemiBold" FontSize="12"/>
|
||||
<Run Text="{Binding SelectedPathOperation.Operation.RequestBodyType}" FontSize="12"/>
|
||||
</TextBlock>
|
||||
</suki:GlassCard>
|
||||
</controls:Card>
|
||||
<Border Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.RequestClasses, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<ItemsRepeater ItemsSource="{Binding SelectedPathOperation.Operation.RequestClasses}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0 4 0 8">
|
||||
<suki:GlassCard IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<suki:GroupBox Header="{Binding Id}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" FontWeight="DemiBold" Margin="0 0 0 4">Request Classes</TextBlock>
|
||||
<ItemsRepeater ItemsSource="{Binding SelectedPathOperation.Operation.RequestClasses}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0 4 0 8">
|
||||
<TextBlock FontSize="12" FontWeight="DemiBold" Text="{Binding Id}"/>
|
||||
<controls:Card IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyFields}"
|
||||
ItemsSource="{Binding PropertyFields}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</controls:Card>
|
||||
<controls:Card Margin="0 0 0 8" IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyEnums}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow DataGridCell">
|
||||
<Setter Property="FontSize" Value="12"></Setter>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
</DataGrid>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="0 0 0 8" IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<suki:GroupBox Header="{Binding Id}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyEnums}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow DataGridCell">
|
||||
<Setter Property="FontSize" Value="12"></Setter>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
</DataGrid>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<suki:GlassCard Margin="0 4">
|
||||
<controls:Card Margin="0 4">
|
||||
<TextBlock>
|
||||
<Run Text="Return value: " FontWeight="DemiBold" FontSize="12"/>
|
||||
<Run Text="{Binding SelectedPathOperation.Operation.ReturnType}" FontSize="12"/>
|
||||
</TextBlock>
|
||||
</suki:GlassCard>
|
||||
</controls:Card>
|
||||
<Border Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.ResponseClasses, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<ItemsRepeater ItemsSource="{Binding SelectedPathOperation.Operation.ResponseClasses}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0 4 0 8">
|
||||
<suki:GlassCard IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<suki:GroupBox Header="{Binding Id}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" FontWeight="DemiBold">Response Classes</TextBlock>
|
||||
<ItemsRepeater ItemsSource="{Binding SelectedPathOperation.Operation.ResponseClasses}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0 4 0 8">
|
||||
<TextBlock FontSize="12" FontWeight="DemiBold" Text="{Binding Id}" Margin="0 0 0 4"/>
|
||||
<controls:Card IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyFields}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow DataGridCell">
|
||||
<Setter Property="FontSize" Value="12"></Setter>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
</DataGrid>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="0 0 0 8" IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<suki:GroupBox Header="{Binding Id}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyEnums}"
|
||||
ItemsSource="{Binding PropertyFields}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow DataGridCell">
|
||||
<Setter Property="FontSize" Value="12"></Setter>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
</DataGrid>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</controls:Card>
|
||||
<controls:Card Margin="0 0 0 8" IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<DataGrid
|
||||
ItemsSource="{Binding PropertyEnums}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
<GridSplitter Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Background="Gray"/>
|
||||
<GridSplitter Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Background="Gray"
|
||||
Margin="8 0 8 0"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="4" Orientation="Horizontal">
|
||||
<Button HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
@@ -301,21 +304,25 @@
|
||||
FontSize="10"
|
||||
Padding="12 4 12 4"
|
||||
Classes="Flat"
|
||||
Content="{Binding SelectedPathOperation.ResponseStatus}"/>
|
||||
Content="{Binding SelectedPathOperation.Response.Value.Status}"/>
|
||||
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="1" Grid.Column="4">
|
||||
<TabControl>
|
||||
<TabItem Header="Preview">
|
||||
<avalonEdit:TextEditor
|
||||
Name="EndpointResponseEditor"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
ShowLineNumbers="True"
|
||||
Text=""
|
||||
FontSize="12"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
<controls:BusyArea BusyText="Loading..."
|
||||
IsBusy="{Binding SelectedPathOperation.IsBusy}">
|
||||
<TabControl>
|
||||
<TabItem Header="Preview">
|
||||
<avalonEdit:TextEditor
|
||||
Name="EndpointResponseEditor"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
ShowLineNumbers="True"
|
||||
IsReadOnly="True"
|
||||
Text=""
|
||||
FontSize="12"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</controls:BusyArea>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,17 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Styling;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.TextMate;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Extensions;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using Needlework.Net.Desktop.ViewModels;
|
||||
using SukiUI;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.ViewModels;
|
||||
using TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
namespace Needlework.Net.Views;
|
||||
|
||||
public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>, IRecipient<ContentRequestMessage>
|
||||
{
|
||||
@@ -23,9 +20,9 @@ public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
base.OnAttachedToVisualTree(e);
|
||||
|
||||
var vm = (EndpointViewModel)DataContext!;
|
||||
_requestEditor = this.FindControl<TextEditor>("EndpointRequestEditor");
|
||||
@@ -37,7 +34,6 @@ public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>
|
||||
WeakReferenceMessenger.Default.Register<ContentRequestMessage, string>(this, "EndpointRequestEditor");
|
||||
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
@@ -45,20 +41,12 @@ public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
SukiTheme.GetInstance().OnBaseThemeChanged -= OnBaseThemeChanged;
|
||||
}
|
||||
|
||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||
{
|
||||
var registryOptions = new RegistryOptions(
|
||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||
|
||||
var requestTmi = _requestEditor.InstallTextMate(registryOptions);
|
||||
requestTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||
.GetLanguageByExtension(".json").Id));
|
||||
var responseTmi = _requestEditor.InstallTextMate(registryOptions);
|
||||
responseTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||
.GetLanguageByExtension(".json").Id));
|
||||
}
|
||||
|
||||
public void Receive(EditorUpdateMessage message)
|
||||
32
Needlework.Net/Views/EndpointsContainerView.axaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
||||
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.EndpointsContainerView"
|
||||
x:DataType="vm:EndpointsContainerViewModel">
|
||||
<Grid RowDefinitions="auto,*"
|
||||
ColumnDefinitions="*"
|
||||
Margin="16">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0 0 0 8">
|
||||
<Button Command="{Binding GoBackCommand}"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
Margin="0 0 8 0">
|
||||
<i:Icon Value="fa-arrow-left"
|
||||
FontSize="20"/>
|
||||
</Button>
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{Binding Title}"/>
|
||||
</StackPanel>
|
||||
<TransitioningContentControl
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Content="{Binding ActiveViewModel}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,6 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
namespace Needlework.Net.Views;
|
||||
|
||||
public partial class EndpointsContainerView : UserControl
|
||||
{
|
||||
32
Needlework.Net/Views/EndpointsView.axaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
Name="EndpointsControl"
|
||||
x:Class="Needlework.Net.Views.EndpointsView"
|
||||
x:DataType="vm:EndpointsViewModel">
|
||||
<controls:BusyArea IsBusy="{Binding IsBusy}"
|
||||
BusyText="Loading...">
|
||||
<Grid RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||
<TextBox Watermark="Search" Margin="0 4" Text="{Binding Search}" Grid.Row="1" Grid.Column="0"/>
|
||||
<ScrollViewer Grid.Row="2" Grid.Column="0">
|
||||
<ItemsRepeater ItemsSource="{Binding Query}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Command="{Binding #EndpointsControl.((vm:EndpointsViewModel)DataContext).OpenEndpointCommand}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{Binding}"
|
||||
Theme="{StaticResource TransparentButton}"/>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</controls:BusyArea>
|
||||
</UserControl>
|
||||
@@ -1,6 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views
|
||||
namespace Needlework.Net.Views
|
||||
{
|
||||
public partial class EndpointsView : UserControl
|
||||
{
|
||||
@@ -2,74 +2,62 @@
|
||||
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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
|
||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.HomeView"
|
||||
x:Class="Needlework.Net.Views.HomeView"
|
||||
x:DataType="vm:HomeViewModel">
|
||||
<!-- TOP LEVEL -->
|
||||
<ScrollViewer>
|
||||
<WrapPanel Margin="8"
|
||||
theme:WrapPanelExtensions.AnimatedScroll="true"
|
||||
Orientation="Horizontal">
|
||||
<!-- WELCOME -->
|
||||
<StackPanel>
|
||||
<suki:GlassCard Margin="8">
|
||||
<Border Margin="12">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h3">Welcome to Needlework.Net</TextBlock>
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">
|
||||
Welcome to Needlework.Net
|
||||
</TextBlock>
|
||||
<TextBlock>Get started with LCU development by clicking on the endpoints tab in the left panel.</TextBlock>
|
||||
</StackPanel>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="8" Classes="Accent">
|
||||
</Border>
|
||||
<controls:Card Margin="12">
|
||||
<TextBlock TextWrapping="Wrap">THE PROGRAM IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGMENT, OR OF FITNESS FOR A PARTICULAR PURPOSE. LICENSOR DOES NOT WARRANT THAT THE FUNCTIONS CONTAINED IN THE PROGRAM WILL MEET YOUR REQUIREMENTS OR THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. LICENSOR MAKES NO WARRANTIES RESPECTING ANY HARM THAT MAY BE CAUSED BY MALICIOUS USE OF THIS SOFTWARE. LICENSOR FURTHER EXPRESSLY DISCLAIMS ANY WARRANTY OR REPRESENTATION TO AUTHORIZED USERS OR TO ANY THIRD PARTY.</TextBlock>
|
||||
</suki:GlassCard>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
<!-- STATUS -->
|
||||
<StackPanel>
|
||||
<suki:GlassCard Margin="8" Width="250">
|
||||
<suki:GroupBox Header="Status">
|
||||
<TextBlock FontSize="24" FontWeight="Bold" Margin="0 4" Foreground="{Binding StatusForeground}" Text="{Binding StatusText}" />
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="8" Width="250">
|
||||
<suki:GroupBox Header="Address">
|
||||
<TextBlock Text="{Binding StatusAddress}"/>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
</StackPanel>
|
||||
<!-- LEGAL -->
|
||||
<suki:GlassCard Margin="8" Width="300">
|
||||
<suki:GroupBox Header="Disclaimer">
|
||||
<TextBlock TextWrapping="Wrap">Needlework.Net isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties. Riot Games, and all associated properties are trademarks or registered trademarks of Riot Games, Inc.</TextBlock>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
<!-- FOOTER -->
|
||||
<StackPanel>
|
||||
<suki:GlassCard Margin="8" Width="400">
|
||||
<controls:Card Margin="12" Width="300">
|
||||
<StackPanel>
|
||||
<TextBlock>© 2024 - Blossomi Shymae</TextBlock>
|
||||
<TextBlock>MIT License</TextBlock>
|
||||
</StackPanel>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="8" Width="400">
|
||||
<suki:GroupBox Header="Resources">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<TextBlock
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}"
|
||||
Margin="0 0 0 8">Resources</TextBlock>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel.Styles>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Command" Value="{Binding OpenUrlCommand}"/>
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
<Button CommandParameter="https://hextechdocs.dev/tag/lcu/" Margin="0 0 16 0">
|
||||
<Button CommandParameter="https://hextechdocs.dev/tag/lcu/" Margin="0 0 8 0">
|
||||
Hextech Docs
|
||||
</Button>
|
||||
<Button CommandParameter="https://hextechdocs.dev/getting-started-with-the-lcu-api/">
|
||||
Getting Started
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</suki:GroupBox>
|
||||
</suki:GlassCard>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
<controls:Card Margin="12" Width="300">
|
||||
<StackPanel>
|
||||
<TextBlock>© 2024 - Blossomi Shymae</TextBlock>
|
||||
<TextBlock>MIT License</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</StackPanel>
|
||||
<!-- LEGAL -->
|
||||
<controls:Card Margin="12" Width="300">
|
||||
<TextBlock TextWrapping="Wrap">Needlework.Net isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties. Riot Games, and all associated properties are trademarks or registered trademarks of Riot Games, Inc.</TextBlock>
|
||||
</controls:Card>
|
||||
</WrapPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -1,6 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views
|
||||
namespace Needlework.Net.Views
|
||||
{
|
||||
public partial class HomeView : UserControl
|
||||
{
|
||||
99
Needlework.Net/Views/MainWindow.axaml
Normal file
@@ -0,0 +1,99 @@
|
||||
<Window
|
||||
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:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Title="Needlework.Net"
|
||||
Icon="/Assets/app.ico"
|
||||
Width="1280"
|
||||
Height="720">
|
||||
<Grid RowDefinitions="auto,*">
|
||||
<Grid ColumnDefinitions="auto,auto,*,auto"
|
||||
Background="Transparent"
|
||||
Height="40"
|
||||
Grid.Row="0">
|
||||
<Image Margin="12 4"
|
||||
IsHitTestVisible="False"
|
||||
Source="/Assets/app.png"
|
||||
Width="18"
|
||||
Height="18"
|
||||
DockPanel.Dock="Left"
|
||||
Grid.Column="0"/>
|
||||
<TextBlock FontSize="12"
|
||||
IsHitTestVisible="False"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="1">
|
||||
Needlework.Net
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<ui:NavigationView AlwaysShowHeader="False"
|
||||
PaneDisplayMode="Left"
|
||||
IsSettingsVisible="False"
|
||||
Grid.Row="1"
|
||||
MenuItemsSource="{Binding MenuItems}"
|
||||
SelectedItem="{Binding SelectedMenuItem}">
|
||||
<ui:NavigationView.PaneFooter>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel.Styles>
|
||||
<Style Selector="materialIcons|MaterialIcon">
|
||||
<Setter Property="Width" Value="20" />
|
||||
<Setter Property="Height" Value="20" />
|
||||
</Style>
|
||||
<Style Selector="i|Icon">
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
<Button
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenUrlCommand}"
|
||||
CommandParameter="https://github.com/BlossomiShymae/Needlework.Net"
|
||||
ToolTip.Tip="Open on GitHub."
|
||||
Margin="4">
|
||||
<materialIcons:MaterialIcon Kind="Github" />
|
||||
</Button>
|
||||
<Button
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenUrlCommand}"
|
||||
CommandParameter="https://discord.gg/chEvEX5J4E"
|
||||
ToolTip.Tip="Open Discord server."
|
||||
Margin="4">
|
||||
<i:Icon Value="fa-brand fa-discord" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</ui:NavigationView.PaneFooter>
|
||||
<Grid>
|
||||
<TransitioningContentControl Content="{Binding CurrentPage}"/>
|
||||
<Button Content="{Binding Version}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="16"/>
|
||||
<ItemsRepeater ItemsSource="{Binding InfoBarItems}"
|
||||
VerticalAlignment="Bottom">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Margin="4">
|
||||
<ui:InfoBar
|
||||
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
|
||||
Title="{Binding Title}"
|
||||
IsOpen="{Binding IsOpen}"
|
||||
Severity="{Binding Severity}"
|
||||
Message="{Binding Message}"
|
||||
ActionButton="{Binding ActionButton}"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Grid>
|
||||
</ui:NavigationView>
|
||||
</Grid>
|
||||
</Window>
|
||||
13
Needlework.Net/Views/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
|
||||
namespace Needlework.Net.Views;
|
||||
|
||||
public partial class MainWindow : AppWindow
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
}
|
||||
}
|
||||
65
Needlework.Net/Views/OopsiesDialog.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||