mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-07 02:30:48 +01:00
WIP
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
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">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
@@ -12,8 +13,14 @@
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme></FluentTheme>
|
||||
<sukiUi:SukiTheme ThemeColor="Blue" />
|
||||
<sukiUi:SukiTheme ThemeColor="Blue" />
|
||||
<materialIcons:MaterialIconStyles />
|
||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
<converters:EnumerableBoolConverter x:Key="EnumerableBoolConverter"/>
|
||||
<converters:NullBoolConverter x:Key="NullBoolConverter"/>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -15,9 +16,14 @@ public partial class App(IServiceProvider serviceProvider) : Application
|
||||
|
||||
public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
};
|
||||
|
||||
public static readonly int MaxCharacters = 10_000;
|
||||
|
||||
public static Window? MainWindow;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
@@ -31,8 +37,11 @@ public partial class App(IServiceProvider serviceProvider) : Application
|
||||
{
|
||||
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
||||
};
|
||||
MainWindow = desktop.MainWindow;
|
||||
}
|
||||
|
||||
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
BIN
Needlework.Net.Desktop/Assets/app.ico
Normal file
BIN
Needlework.Net.Desktop/Assets/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
BIN
Needlework.Net.Desktop/Assets/app.png
Normal file
BIN
Needlework.Net.Desktop/Assets/app.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
22
Needlework.Net.Desktop/Converters/EnumerableBoolConverter.cs
Normal file
22
Needlework.Net.Desktop/Converters/EnumerableBoolConverter.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Desktop.Converters
|
||||
{
|
||||
public class EnumerableBoolConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is IEnumerable<object> values) return values.Any();
|
||||
return false;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Needlework.Net.Desktop/Converters/NullBoolConverter.cs
Normal file
19
Needlework.Net.Desktop/Converters/NullBoolConverter.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Needlework.Net.Desktop.Converters
|
||||
{
|
||||
public class NullBoolConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Needlework.Net.Desktop/Extensions/TextEditorExtensions.cs
Normal file
29
Needlework.Net.Desktop/Extensions/TextEditorExtensions.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Avalonia.Media;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Highlighting;
|
||||
using AvaloniaEdit.Indentation.CSharp;
|
||||
|
||||
namespace Needlework.Net.Desktop.Extensions
|
||||
{
|
||||
public static class TextEditorExtensions
|
||||
{
|
||||
public static void ApplyJsonEditorSettings(this TextEditor textEditor)
|
||||
{
|
||||
textEditor.TextArea.IndentationStrategy = new CSharpIndentationStrategy(textEditor.Options);
|
||||
textEditor.TextArea.RightClickMovesCaret = true;
|
||||
textEditor.TextArea.Options.EnableHyperlinks = false;
|
||||
textEditor.TextArea.Options.EnableEmailHyperlinks = false;
|
||||
textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("Json");
|
||||
|
||||
var purple = Color.FromRgb(189, 147, 249);
|
||||
var yellow = Color.FromRgb(241, 250, 140);
|
||||
var cyan = Color.FromRgb(139, 233, 253);
|
||||
textEditor.SyntaxHighlighting.GetNamedColor("Bool").Foreground = new SimpleHighlightingBrush(purple);
|
||||
textEditor.SyntaxHighlighting.GetNamedColor("Number").Foreground = new SimpleHighlightingBrush(purple);
|
||||
textEditor.SyntaxHighlighting.GetNamedColor("String").Foreground = new SimpleHighlightingBrush(yellow);
|
||||
textEditor.SyntaxHighlighting.GetNamedColor("Null").Foreground = new SimpleHighlightingBrush(purple);
|
||||
textEditor.SyntaxHighlighting.GetNamedColor("FieldName").Foreground = new SimpleHighlightingBrush(cyan);
|
||||
textEditor.SyntaxHighlighting.GetNamedColor("Punctuation").Foreground = new SimpleHighlightingBrush(yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Needlework.Net.Desktop/Messages/ContentRequestMessage.cs
Normal file
8
Needlework.Net.Desktop/Messages/ContentRequestMessage.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class ContentRequestMessage : RequestMessage<string>
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Needlework.Net.Desktop/Messages/DataReadyMessage.cs
Normal file
9
Needlework.Net.Desktop/Messages/DataReadyMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Core;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class DataReadyMessage(LcuSchemaHandler handler) : ValueChangedMessage<LcuSchemaHandler>(handler)
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Needlework.Net.Desktop/Messages/DataRequestMessage.cs
Normal file
9
Needlework.Net.Desktop/Messages/DataRequestMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Needlework.Net.Core;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class DataRequestMessage : RequestMessage<LcuSchemaHandler>
|
||||
{
|
||||
}
|
||||
}
|
||||
20
Needlework.Net.Desktop/Messages/EditorUpdateMessage.cs
Normal file
20
Needlework.Net.Desktop/Messages/EditorUpdateMessage.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class EditorUpdateMessage(EditorUpdate editorUpdate) : ValueChangedMessage<EditorUpdate>(editorUpdate)
|
||||
{
|
||||
}
|
||||
|
||||
public class EditorUpdate
|
||||
{
|
||||
public string Text { get; }
|
||||
public string Key { get; }
|
||||
|
||||
public EditorUpdate(string text, string key)
|
||||
{
|
||||
Text = text;
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class HostDocumentRequestMessage : RequestMessage<OpenApiDocument>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class OopsiesWindowCanceledMessage(object? data) : ValueChangedMessage<object?>(data)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class OopsiesWindowRequestedMessage(string text) : ValueChangedMessage<string>(text)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Needlework.Net.Desktop.Messages
|
||||
{
|
||||
public class ResponseUpdatedMessage(string data) : ValueChangedMessage<string>(data)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Avalonia">
|
||||
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
||||
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
<AssemblyVersion>0.1.0.0</AssemblyVersion>
|
||||
<FileVersion>0.1.0.0</FileVersion>
|
||||
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
||||
@@ -18,12 +18,15 @@
|
||||
<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" />
|
||||
<!--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.8.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
@@ -45,4 +48,17 @@
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Views\AboutView.axaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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="Utilities\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -35,7 +35,7 @@ class Program
|
||||
var builder = new ServiceCollection();
|
||||
|
||||
builder.AddSingleton<MainWindowViewModel>();
|
||||
builder.AddSingleton<DialogService>();
|
||||
builder.AddSingleton<WindowService>();
|
||||
// Dynamically add ViewModels
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
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 DialogService
|
||||
{
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public Dictionary<string, SukiWindow> Dialogs { get; } = [];
|
||||
|
||||
public DialogService(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void ShowEndpoint(string endpoint)
|
||||
{
|
||||
if (!Dialogs.TryGetValue(endpoint, out var _))
|
||||
{
|
||||
var dialog = new EndpointView();
|
||||
dialog.DataContext = new EndpointViewModel(endpoint);
|
||||
dialog.Show();
|
||||
dialog.Closed += OnDialogClosed;
|
||||
Dialogs[endpoint] = dialog;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDialogClosed(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender == null)
|
||||
return;
|
||||
|
||||
var dialog = (SukiWindow)sender;
|
||||
if (dialog.DataContext is EndpointViewModel vm)
|
||||
{
|
||||
Dialogs.Remove(vm.Endpoint);
|
||||
dialog.DataContext = null;
|
||||
}
|
||||
|
||||
dialog.Closed -= OnDialogClosed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.Core;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Desktop.Services
|
||||
{
|
||||
public partial class LcuService : ObservableObject
|
||||
{
|
||||
public HttpClient HttpClient { get; }
|
||||
|
||||
public LcuSchemaHandler LcuSchemaHandler { get; }
|
||||
|
||||
[ObservableProperty] private string _statusText = "Offline";
|
||||
[ObservableProperty] private IBrush _statusColor = new SolidColorBrush(Colors.Red.ToUInt32());
|
||||
[ObservableProperty] private string _statusAddress = "N/A";
|
||||
|
||||
public LcuService(HttpClient httpClient)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
|
||||
Task.Run(ProcessBackground);
|
||||
}
|
||||
|
||||
private void ProcessBackground()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Needlework.Net.Desktop/Services/WindowService.cs
Normal file
53
Needlework.Net.Desktop/Services/WindowService.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
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,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.Desktop
|
||||
{
|
||||
public class TextUpdatedEventArgs(string text) : EventArgs
|
||||
{
|
||||
public string Text { get; } = text;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Needlework.Net.Desktop.ViewModels;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.Desktop
|
||||
{
|
||||
@@ -9,27 +9,20 @@ namespace Needlework.Net.Desktop
|
||||
{
|
||||
public Control? Build(object? param)
|
||||
{
|
||||
if (param is null)
|
||||
return new TextBlock { Text = "data was null" };
|
||||
if (param is null) return new TextBlock { Text = "data was null" };
|
||||
|
||||
var name = param.GetType().FullName!.Replace("ViewModels", "Views")
|
||||
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 };
|
||||
}
|
||||
if (type != null) return (Control)Activator.CreateInstance(type)!;
|
||||
else return new TextBlock { Text = "Not Found: " + name };
|
||||
}
|
||||
|
||||
public bool Match(object? data)
|
||||
{
|
||||
if (data is PageBase) return true;
|
||||
return false;
|
||||
return data is INotifyPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,25 @@
|
||||
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 Needlework.Net.Desktop.Views;
|
||||
using SukiUI.Controls;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class ConsoleViewModel : PageBase
|
||||
public partial class ConsoleViewModel : PageBase, IRecipient<OopsiesWindowRequestedMessage>, IRecipient<DataReadyMessage>
|
||||
{
|
||||
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
||||
|
||||
[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;
|
||||
@@ -20,10 +28,14 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
[ObservableProperty] private string? _responseStatus = null;
|
||||
[ObservableProperty] private string? _responseAuthentication = null;
|
||||
|
||||
public event EventHandler<TextUpdatedEventArgs>? ResponseBodyUpdated;
|
||||
public WindowService WindowService { get; }
|
||||
|
||||
public ConsoleViewModel() : base("Console", Material.Icons.MaterialIconKind.Console, -100)
|
||||
public ConsoleViewModel(WindowService windowService) : base("Console", Material.Icons.MaterialIconKind.Console, -200)
|
||||
{
|
||||
WindowService = windowService;
|
||||
|
||||
WeakReferenceMessenger.Default.Register<OopsiesWindowRequestedMessage, string>(this, nameof(ConsoleView));
|
||||
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -31,6 +43,7 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
IsRequestBusy = true;
|
||||
if (string.IsNullOrEmpty(RequestPath)) throw new Exception("Path is empty.");
|
||||
|
||||
var method = RequestMethodSelected switch
|
||||
@@ -47,29 +60,43 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
};
|
||||
|
||||
var processInfo = Connector.GetProcessInfo();
|
||||
var response = await Connector.SendAsync(method, RequestPath) ?? throw new Exception("Response is null.");
|
||||
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 riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ResponseStatus = response.StatusCode.ToString();
|
||||
ResponsePath = $"https://127.0.0.1/{processInfo.AppPort}{RequestPath}";
|
||||
ResponseAuthentication = riotAuthentication.Value;
|
||||
ResponseBodyUpdated?.Invoke(this, new(body));
|
||||
});
|
||||
ResponseStatus = response.StatusCode.ToString();
|
||||
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
|
||||
ResponseAuthentication = $"Basic {riotAuthentication.Value}";
|
||||
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(body), nameof(ConsoleViewModel));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ResponseStatus = null;
|
||||
ResponsePath = null;
|
||||
ResponseAuthentication = null;
|
||||
ResponseBodyUpdated?.Invoke(this, new(string.Empty));
|
||||
});
|
||||
ResponseStatus = null;
|
||||
ResponsePath = null;
|
||||
ResponseAuthentication = null;
|
||||
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRequestBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(OopsiesWindowRequestedMessage message)
|
||||
{
|
||||
WindowService.ShowOopsiesWindow(message.Value);
|
||||
}
|
||||
|
||||
public void Receive(DataReadyMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
RequestPaths = new AvaloniaList<string>([.. message.Value.Paths]);
|
||||
IsBusy = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using SukiUI.Controls;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class EndpointViewModel(string endpoint) : ObservableObject
|
||||
public partial class EndpointViewModel : ObservableObject, ISukiStackPageTitleProvider
|
||||
{
|
||||
public string Endpoint { get; } = endpoint;
|
||||
public string Title => $"Needlework.Net - {Endpoint}";
|
||||
public string Endpoint { get; }
|
||||
public string Title => Endpoint;
|
||||
|
||||
[ObservableProperty] private IAvaloniaReadOnlyList<PathOperationViewModel> _pathOperations;
|
||||
[ObservableProperty] private PathOperationViewModel? _selectedPathOperation;
|
||||
|
||||
public EndpointViewModel(string endpoint)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
|
||||
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
|
||||
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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,57 +1,55 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.Core;
|
||||
using Needlework.Net.Desktop.Services;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using SukiUI.Controls;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class EndpointsViewModel : PageBase
|
||||
public partial class EndpointsViewModel : ObservableObject, IRecipient<DataReadyMessage>, ISukiStackPageTitleProvider
|
||||
{
|
||||
public HttpClient HttpClient { get; }
|
||||
|
||||
public DialogService DialogService { get; }
|
||||
public string Title => "Endpoints";
|
||||
public Action<ISukiStackPageTitleProvider> OnClicked;
|
||||
|
||||
[ObservableProperty] private List<string> _plugins = [];
|
||||
[ObservableProperty] private IAvaloniaReadOnlyList<string> _plugins = new AvaloniaList<string>();
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
[ObservableProperty] private string _search = string.Empty;
|
||||
[ObservableProperty] private List<string> _query = [];
|
||||
[ObservableProperty] private IAvaloniaReadOnlyList<string> _query = new AvaloniaList<string>();
|
||||
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
||||
|
||||
public EndpointsViewModel(HttpClient httpClient, DialogService dialogService) : base("Endpoints", Material.Icons.MaterialIconKind.Hub, -500)
|
||||
public EndpointsViewModel(HttpClient httpClient, Action<ISukiStackPageTitleProvider> onClicked)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
DialogService = dialogService;
|
||||
OnClicked = onClicked;
|
||||
|
||||
Task.Run(InitializeAsync);
|
||||
WeakReferenceMessenger.Default.Register(this);
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
public void Receive(DataReadyMessage message)
|
||||
{
|
||||
var handler = new LcuSchemaHandler(await Resources.GetOpenApiDocumentAsync(HttpClient));
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
Plugins = [.. handler.Plugins.Keys];
|
||||
Query = [.. Plugins];
|
||||
IsBusy = false;
|
||||
});
|
||||
IsBusy = false;
|
||||
Plugins = new AvaloniaList<string>([.. message.Value.Plugins.Keys]);
|
||||
Query = new AvaloniaList<string>([.. Plugins]);
|
||||
}
|
||||
|
||||
partial void OnSearchChanged(string value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Search))
|
||||
Query = Plugins.Where(x => x.Contains(value)).ToList();
|
||||
Query = new AvaloniaList<string>(Plugins.Where(x => x.Contains(value)));
|
||||
else
|
||||
Query = Plugins;
|
||||
}
|
||||
|
||||
partial void OnSelectedQueryChanged(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return;
|
||||
DialogService.ShowEndpoint(value);
|
||||
if (string.IsNullOrEmpty(value)) return;
|
||||
|
||||
OnClicked.Invoke(new EndpointViewModel(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
@@ -16,27 +16,40 @@ namespace Needlework.Net.Desktop.ViewModels
|
||||
|
||||
public HomeViewModel() : base("Home", Material.Icons.MaterialIconKind.Home, int.MinValue)
|
||||
{
|
||||
Task.Run(async () => { while (true) { SetStatus(); await Task.Delay(TimeSpan.FromSeconds(5)); } });
|
||||
StartProcessing();
|
||||
}
|
||||
|
||||
private void SetStatus()
|
||||
private void StartProcessing()
|
||||
{
|
||||
void Set(string text, Color color, string address)
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
StatusText = text;
|
||||
StatusForeground = new SolidColorBrush(color.ToUInt32());
|
||||
StatusAddress = address;
|
||||
}
|
||||
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();
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() => Set("Online", Colors.Green, $"https://127.0.0.1:{processInfo.AppPort}/"));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() => Set("Offline", Colors.Red, "N/A"));
|
||||
}
|
||||
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));
|
||||
}
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -1,22 +1,60 @@
|
||||
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 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
|
||||
public partial class MainWindowViewModel : ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>
|
||||
{
|
||||
public IAvaloniaReadOnlyList<PageBase> Pages { get; }
|
||||
|
||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||
|
||||
public MainWindowViewModel(IEnumerable<PageBase> pages)
|
||||
public HttpClient HttpClient { get; }
|
||||
public LcuSchemaHandler? LcuSchemaHandler { get; set; }
|
||||
public OpenApiDocument? HostDocument { get; set; }
|
||||
|
||||
[ObservableProperty] private bool _isBusy = true;
|
||||
|
||||
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient)
|
||||
{
|
||||
Pages = new AvaloniaList<PageBase>(pages.OrderBy(x => x.Index).ThenBy(x => x.DisplayName));
|
||||
HttpClient = httpClient;
|
||||
|
||||
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]
|
||||
|
||||
29
Needlework.Net.Desktop/ViewModels/OopsiesWindowViewModel.cs
Normal file
29
Needlework.Net.Desktop/ViewModels/OopsiesWindowViewModel.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Needlework.Net.Desktop/ViewModels/OperationViewModel.cs
Normal file
153
Needlework.Net.Desktop/ViewModels/OperationViewModel.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Desktop.Messages;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class OperationViewModel : ObservableObject
|
||||
{
|
||||
public string Summary { get; }
|
||||
public string Description { get; }
|
||||
public string ReturnType { get; }
|
||||
public bool IsRequestBody { get; }
|
||||
public string? RequestBodyType { get; }
|
||||
public IAvaloniaReadOnlyList<PropertyClassViewModel> RequestClasses { get; }
|
||||
public IAvaloniaReadOnlyList<PropertyClassViewModel> ResponseClasses { get; }
|
||||
public IAvaloniaReadOnlyList<ParameterViewModel> PathParameters { get; }
|
||||
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
|
||||
|
||||
public OperationViewModel(OpenApiOperation operation)
|
||||
{
|
||||
Summary = operation.Summary ?? string.Empty;
|
||||
Description = operation.Description ?? string.Empty;
|
||||
IsRequestBody = operation.RequestBody != null;
|
||||
ReturnType = GetReturnType(operation.Responses);
|
||||
RequestClasses = GetRequestClasses(operation.RequestBody);
|
||||
ResponseClasses = GetResponseClasses(operation.Responses);
|
||||
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
|
||||
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
|
||||
RequestBodyType = GetRequestBodyType(operation.RequestBody);
|
||||
}
|
||||
|
||||
private string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
||||
{
|
||||
if (requestBody == null) return null;
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private AvaloniaList<ParameterViewModel> GetParameters(IList<OpenApiParameter> parameters, ParameterLocation location)
|
||||
{
|
||||
var pathParameters = new AvaloniaList<ParameterViewModel>();
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
if (parameter.In != location) break;
|
||||
pathParameters.Add(new ParameterViewModel(parameter.Name, parameter.Schema.Type, parameter.Required));
|
||||
}
|
||||
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses)
|
||||
{
|
||||
if (responses.TryGetValue("2XX", out var response)
|
||||
&& response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
||||
var schema = media.Schema;
|
||||
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(schema, propertyClasses, document);
|
||||
return propertyClasses;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private void WalkSchema(OpenApiSchema schema, AvaloniaList<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||
{
|
||||
var type = GetSchemaType(schema);
|
||||
if (IsComponent(type))
|
||||
{
|
||||
string componentId = GetComponentId(schema);
|
||||
var componentSchema = document.Components.Schemas[componentId];
|
||||
var responseClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
||||
propertyClasses.Add(responseClass);
|
||||
|
||||
foreach ((var _, var property) in componentSchema.Properties)
|
||||
// Check for self-references like "LolLootLootOddsResponse"
|
||||
// I blame dubble
|
||||
if (IsComponent(GetSchemaType(property)) && componentId != GetComponentId(property))
|
||||
WalkSchema(property, propertyClasses, document);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetComponentId(OpenApiSchema schema)
|
||||
{
|
||||
string componentId;
|
||||
if (schema.Reference != null) componentId = schema.Reference.Id;
|
||||
else if (schema.Items != null) componentId = schema.Items.Reference.Id;
|
||||
else componentId = schema.AdditionalProperties.Reference.Id;
|
||||
return componentId;
|
||||
}
|
||||
|
||||
private static bool IsComponent(string type)
|
||||
{
|
||||
return !(type.Contains("object")
|
||||
|| type.Contains("array")
|
||||
|| type.Contains("bool")
|
||||
|| type.Contains("string")
|
||||
|| type.Contains("integer")
|
||||
|| type.Contains("number"));
|
||||
}
|
||||
|
||||
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody)
|
||||
{
|
||||
if (requestBody == null) return [];
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
var type = GetSchemaType(media.Schema);
|
||||
if (IsComponent(type))
|
||||
{
|
||||
var componentId = GetComponentId(schema);
|
||||
var componentSchema = document.Components.Schemas[componentId];
|
||||
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(componentSchema, propertyClasses, document);
|
||||
return propertyClasses;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private string GetReturnType(OpenApiResponses responses)
|
||||
{
|
||||
if (responses.TryGetValue("2XX", out var response)
|
||||
&& response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
public static string GetSchemaType(OpenApiSchema schema)
|
||||
{
|
||||
if (schema.Reference != null) return schema.Reference.Id;
|
||||
if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||
if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}";
|
||||
if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]";
|
||||
if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]";
|
||||
if (schema.Type == "array") return $"{schema.Items.Type}[]";
|
||||
return schema.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Needlework.Net.Desktop/ViewModels/ParameterViewModel.cs
Normal file
20
Needlework.Net.Desktop/ViewModels/ParameterViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class ParameterViewModel : ObservableObject
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Type { get; }
|
||||
public bool IsRequired { get; }
|
||||
[ObservableProperty] private string? _value = null;
|
||||
|
||||
public ParameterViewModel(string name, string type, bool isRequired, string? value = null)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
IsRequired = isRequired;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Needlework.Net.Desktop/ViewModels/PathOperationViewModel.cs
Normal file
134
Needlework.Net.Desktop/ViewModels/PathOperationViewModel.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
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 ex)
|
||||
{
|
||||
Task.Run(async () => await SukiHost.ShowToast("Error", ex.Message, SukiUI.Enums.NotificationType.Error));
|
||||
}
|
||||
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;
|
||||
|
||||
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;
|
||||
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(responseBody, "EndpointResponseEditor")));
|
||||
}
|
||||
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,7 +0,0 @@
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public class PluginViewModel
|
||||
{
|
||||
public PluginViewModel() { }
|
||||
}
|
||||
}
|
||||
36
Needlework.Net.Desktop/ViewModels/PropertyClassViewModel.cs
Normal file
36
Needlework.Net.Desktop/ViewModels/PropertyClassViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public class PropertyClassViewModel : ObservableObject
|
||||
{
|
||||
public string Id { get; }
|
||||
public IAvaloniaReadOnlyList<PropertyFieldViewModel> PropertyFields { get; } = new AvaloniaList<PropertyFieldViewModel>();
|
||||
public IAvaloniaReadOnlyList<PropertyEnumViewModel> PropertyEnums { get; } = new AvaloniaList<PropertyEnumViewModel>();
|
||||
|
||||
public PropertyClassViewModel(string id, IDictionary<string, OpenApiSchema> properties, IList<IOpenApiAny> enumValue)
|
||||
{
|
||||
AvaloniaList<PropertyFieldViewModel> propertyFields = [];
|
||||
AvaloniaList<PropertyEnumViewModel> propertyEnums = [];
|
||||
foreach ((var propertyName, var propertySchema) in properties)
|
||||
{
|
||||
var type = OperationViewModel.GetSchemaType(propertySchema);
|
||||
var field = new PropertyFieldViewModel(propertyName, type);
|
||||
propertyFields.Add(field);
|
||||
}
|
||||
if (enumValue != null && enumValue.Any())
|
||||
{
|
||||
var propertyEnum = new PropertyEnumViewModel(enumValue);
|
||||
propertyEnums.Add(propertyEnum);
|
||||
}
|
||||
PropertyFields = propertyFields;
|
||||
PropertyEnums = propertyEnums;
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Needlework.Net.Desktop/ViewModels/PropertyEnumViewModel.cs
Normal file
17
Needlework.Net.Desktop/ViewModels/PropertyEnumViewModel.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Microsoft.OpenApi.Any;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public class PropertyEnumViewModel
|
||||
{
|
||||
public string Type { get; } = "Enum";
|
||||
public string Values { get; }
|
||||
|
||||
public PropertyEnumViewModel(IList<IOpenApiAny> enumValue)
|
||||
{
|
||||
Values = $"[{string.Join(", ", enumValue.Select(x => ((OpenApiString)x).Value).ToList())}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Needlework.Net.Desktop/ViewModels/PropertyFieldViewModel.cs
Normal file
14
Needlework.Net.Desktop/ViewModels/PropertyFieldViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public class PropertyFieldViewModel
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Type { get; }
|
||||
|
||||
public PropertyFieldViewModel(string name, string type)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
Needlework.Net.Desktop/ViewModels/WebsocketViewModel.cs
Normal file
103
Needlework.Net.Desktop/ViewModels/WebsocketViewModel.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using BlossomiShymae.GrrrLCU;
|
||||
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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Websocket.Client;
|
||||
|
||||
namespace Needlework.Net.Desktop.ViewModels
|
||||
{
|
||||
public partial class WebsocketViewModel : PageBase
|
||||
{
|
||||
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
||||
[ObservableProperty] private ObservableCollection<string> _eventLog = [];
|
||||
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
||||
[ObservableProperty] private string _search = string.Empty;
|
||||
[ObservableProperty] private bool _isAttach = true;
|
||||
[ObservableProperty] private bool _isTail = false;
|
||||
[ObservableProperty] private string? _selectedEventLog = null;
|
||||
|
||||
private Dictionary<string, EventMessage> _events = [];
|
||||
|
||||
public WindowService WindowService { get; }
|
||||
|
||||
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)
|
||||
{
|
||||
WindowService = windowService;
|
||||
|
||||
var client = Connector.CreateLcuWebsocketClient();
|
||||
client.EventReceived.Subscribe(OnMessage);
|
||||
client.DisconnectionHappened.Subscribe(OnDisconnection);
|
||||
client.ReconnectionHappened.Subscribe(OnReconnection);
|
||||
|
||||
client.Start();
|
||||
client.Send(new EventMessage(RequestType.Subscribe, EventMessage.Kinds.OnJsonApiEvent));
|
||||
}
|
||||
|
||||
[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));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReconnection(ReconnectionInfo info)
|
||||
{
|
||||
Trace.WriteLine($"-- Reconnection --\n{JsonSerializer.Serialize(info, App.JsonSerializerOptions)}");
|
||||
}
|
||||
|
||||
private void OnDisconnection(DisconnectionInfo info)
|
||||
{
|
||||
Trace.WriteLine($"-- Disconnection --\n{JsonSerializer.Serialize(info, App.JsonSerializerOptions)}");
|
||||
}
|
||||
|
||||
private void OnMessage(EventMessage message)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
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);
|
||||
|
||||
log.Add(line);
|
||||
_events[line] = message;
|
||||
}
|
||||
|
||||
EventLog = []; // This is a hack needed to update for ListBox
|
||||
EventLog = new ObservableCollection<string>(log);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.AboutView"
|
||||
x:DataType="vm:AboutViewModel">
|
||||
<ScrollViewer>
|
||||
<WrapPanel Margin="8"
|
||||
<Grid Margin="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<WrapPanel
|
||||
theme:WrapPanelExtensions.AnimatedScroll="true"
|
||||
Orientation="Horizontal">
|
||||
<suki:GlassCard Margin="8">
|
||||
@@ -27,13 +29,13 @@
|
||||
<suki:GlassCard Width="400" Margin="8">
|
||||
<suki:GroupBox Header="About">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Needlework.Net is the sister project 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
|
||||
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>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -9,57 +9,78 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.ConsoleView"
|
||||
x:DataType="vm:ConsoleViewModel">
|
||||
<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"/>
|
||||
<TextBox Text="{Binding RequestPath}"
|
||||
Grid.Row="0" Grid.Column="1"
|
||||
Watermark="E.g. /lol-summoner/v1/current-summoner"/>
|
||||
<TextBox Text="{Binding RequestBody}" Height="200" AcceptsReturn="True" TextWrapping="Wrap"
|
||||
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}">
|
||||
Send
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel
|
||||
Margin="0 0 8 0"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0">
|
||||
<suki:GlassCard Margin="0 4">
|
||||
<TextBlock Text="{Binding ResponsePath}"/>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="0 4">
|
||||
<TextBlock Text="{Binding ResponseStatus}"/>
|
||||
</suki:GlassCard>
|
||||
<suki:GlassCard Margin="0 4">
|
||||
<TextBlock Text="{Binding ResponseAuthentication}" />
|
||||
</suki:GlassCard>
|
||||
</StackPanel>
|
||||
<suki:GlassCard
|
||||
Margin="0 8"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1">
|
||||
<avaloniaEdit:TextEditor Name="ResponseEditor"
|
||||
<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=""
|
||||
FontFamily="Cascadia Code,Consolas,Menlo,Monospace"
|
||||
ShowLineNumbers="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
FontWeight="Light"
|
||||
FontSize="14"/>
|
||||
</suki:GlassCard>
|
||||
</Grid>
|
||||
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="Authentication">
|
||||
<TextBlock Text="{Binding ResponseAuthentication}" />
|
||||
</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>
|
||||
|
||||
@@ -3,9 +3,10 @@ using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Styling;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Highlighting;
|
||||
using AvaloniaEdit.Indentation.CSharp;
|
||||
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 System.Text.Json;
|
||||
@@ -13,35 +14,58 @@ using TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
|
||||
public partial class ConsoleView : UserControl
|
||||
public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessage>, IRecipient<ContentRequestMessage>
|
||||
{
|
||||
private TextEditor? _responseEditor;
|
||||
private TextEditor? _requestEditor;
|
||||
|
||||
public ConsoleView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Receive(ResponseUpdatedMessage message)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(message.Value))
|
||||
{
|
||||
var text = JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(message.Value), App.JsonSerializerOptions);
|
||||
if (text.Length >= App.MaxCharacters)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new OopsiesWindowRequestedMessage(text), nameof(ConsoleView));
|
||||
_responseEditor!.Text = string.Empty;
|
||||
}
|
||||
else _responseEditor!.Text = text;
|
||||
}
|
||||
else _responseEditor!.Text = message.Value;
|
||||
}
|
||||
|
||||
public void Receive(ContentRequestMessage message)
|
||||
{
|
||||
message.Reply(_requestEditor!.Text);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
||||
_responseEditor!.TextArea.IndentationStrategy = new CSharpIndentationStrategy(_responseEditor.Options);
|
||||
_responseEditor!.TextArea.RightClickMovesCaret = true;
|
||||
_responseEditor!.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("JavaScript");
|
||||
_requestEditor = this.FindControl<TextEditor>("RequestEditor");
|
||||
_responseEditor?.ApplyJsonEditorSettings();
|
||||
_requestEditor?.ApplyJsonEditorSettings();
|
||||
|
||||
((ConsoleViewModel)DataContext!)!.ResponseBodyUpdated += ConsoleView_ResponseBodyUpdated;
|
||||
WeakReferenceMessenger.Default.Register<ResponseUpdatedMessage, string>(this, nameof(ConsoleViewModel));
|
||||
WeakReferenceMessenger.Default.Register<ContentRequestMessage, string>(this, "ConsoleRequestEditor");
|
||||
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
|
||||
}
|
||||
|
||||
private void ConsoleView_ResponseBodyUpdated(object? sender, TextUpdatedEventArgs e)
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Text))
|
||||
_responseEditor!.Text = JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(e.Text), App.JsonSerializerOptions);
|
||||
else _responseEditor!.Text = e.Text;
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
SukiTheme.GetInstance().OnBaseThemeChanged -= OnBaseThemeChanged;
|
||||
}
|
||||
|
||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||
@@ -49,8 +73,11 @@ public partial class ConsoleView : UserControl
|
||||
var registryOptions = new RegistryOptions(
|
||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||
|
||||
var textMateInstallation = _responseEditor.InstallTextMate(registryOptions);
|
||||
textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,313 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
<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.EndpointView"
|
||||
x:DataType="vm:EndpointViewModel"
|
||||
Title="{Binding Title}"
|
||||
Width="1280"
|
||||
Height="720">
|
||||
<Grid Margin="8" RowDefinitions="auto,*" ColumnDefinitions="*">
|
||||
<TextBlock Classes="h3" Grid.Row="0" Grid.Column="0" Text="{Binding Endpoint}"/>
|
||||
<ScrollViewer Grid.Row="1" Grid.Column="0">
|
||||
|
||||
</ScrollViewer>
|
||||
x:DataType="vm:EndpointViewModel">
|
||||
<Grid RowDefinitions="auto,*" ColumnDefinitions="3*,2,4*,2,4*">
|
||||
<Grid Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="2"
|
||||
RowDefinitions="auto,*"
|
||||
ColumnDefinitions="*">
|
||||
<ListBox ItemsSource="{Binding PathOperations}"
|
||||
SelectedItem="{Binding SelectedPathOperation}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Visible"
|
||||
Margin="0 0 0 0"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="auto,*">
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Classes="Flat"
|
||||
Margin="0 0 8 0"
|
||||
Content="{Binding Method}"
|
||||
Background="{Binding Color}"
|
||||
FontSize="8"
|
||||
Width="45"
|
||||
Padding="10 2 10 2"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"/>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Path}"
|
||||
FontSize="11"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
<GridSplitter Background="Gray"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"/>
|
||||
<Grid Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
RowDefinitions="*"
|
||||
ColumnDefinitions="auto,*,auto">
|
||||
<TextBox Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="{Binding SelectedPathOperation.Method}"
|
||||
FontSize="12"
|
||||
IsReadOnly="True"/>
|
||||
<TextBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
FontSize="12"
|
||||
Text="{Binding SelectedPathOperation.ResponsePath}"
|
||||
IsReadOnly="True"/>
|
||||
<StackPanel Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Orientation="Horizontal">
|
||||
<Button Classes="Flat"
|
||||
Margin="4"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="12 4 12 4"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding SelectedPathOperation.SendRequestCommand}">Send</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" Grid.Column="2">
|
||||
<TabControl>
|
||||
<TabItem Header="Params">
|
||||
<ScrollViewer>
|
||||
<StackPanel IsVisible="{Binding SelectedPathOperation, Converter={StaticResource NullBoolConverter}}">
|
||||
<suki:GroupBox Header="Path Parameters"
|
||||
Margin="0 4"
|
||||
IsVisible="{Binding SelectedPathOperation.Operation.PathParameters, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<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"
|
||||
Margin="0 4"
|
||||
IsVisible="{Binding SelectedPathOperation.Operation.QueryParameters, Converter={StaticResource EnumerableBoolConverter}}">
|
||||
<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>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="Body">
|
||||
<avalonEdit:TextEditor
|
||||
Name="EndpointRequestEditor"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
Text=""
|
||||
ShowLineNumbers="True"
|
||||
FontSize="12"/>
|
||||
</TabItem>
|
||||
<TabItem Header="Auth">
|
||||
<Grid RowDefinitions="auto,auto,auto,*" ColumnDefinitions="*,4*">
|
||||
<TextBlock FontSize="12"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center">
|
||||
Username
|
||||
</TextBlock>
|
||||
<TextBox FontSize="12"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.ResponseUsername}" />
|
||||
<TextBlock FontSize="12"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center">
|
||||
Password
|
||||
</TextBlock>
|
||||
<TextBox FontSize="12"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.ResponsePassword}"/>
|
||||
<TextBlock FontSize="12"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center">
|
||||
Authorization
|
||||
</TextBlock>
|
||||
<TextBox FontSize="12"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedPathOperation.ResponseAuthorization}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Schemas">
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<suki:GlassCard 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>
|
||||
<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}">
|
||||
<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}"
|
||||
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>
|
||||
</Border>
|
||||
<suki:GlassCard Margin="0 4">
|
||||
<TextBlock>
|
||||
<Run Text="Return value: " FontWeight="DemiBold" FontSize="12"/>
|
||||
<Run Text="{Binding SelectedPathOperation.Operation.ReturnType}" FontSize="12"/>
|
||||
</TextBlock>
|
||||
</suki:GlassCard>
|
||||
<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}">
|
||||
<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}"
|
||||
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>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
<GridSplitter Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Background="Gray"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="4" Orientation="Horizontal">
|
||||
<Button HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="4"
|
||||
FontSize="10"
|
||||
Padding="12 4 12 4"
|
||||
Classes="Flat"
|
||||
Content="{Binding SelectedPathOperation.ResponseStatus}"/>
|
||||
|
||||
</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>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,11 +1,83 @@
|
||||
using SukiUI.Controls;
|
||||
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 TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
|
||||
public partial class EndpointView : SukiWindow
|
||||
public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>, IRecipient<ContentRequestMessage>
|
||||
{
|
||||
private TextEditor? _requestEditor;
|
||||
private TextEditor? _responseEditor;
|
||||
|
||||
public EndpointView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
var vm = (EndpointViewModel)DataContext!;
|
||||
_requestEditor = this.FindControl<TextEditor>("EndpointRequestEditor");
|
||||
_responseEditor = this.FindControl<TextEditor>("EndpointResponseEditor");
|
||||
_requestEditor?.ApplyJsonEditorSettings();
|
||||
_responseEditor?.ApplyJsonEditorSettings();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<EditorUpdateMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ContentRequestMessage, string>(this, "EndpointRequestEditor");
|
||||
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
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)
|
||||
{
|
||||
switch (message.Value.Key)
|
||||
{
|
||||
case "EndpointRequestEditor":
|
||||
_requestEditor!.Text = message.Value.Text;
|
||||
break;
|
||||
case "EndpointResponseEditor":
|
||||
_responseEditor!.Text = message.Value.Text;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(ContentRequestMessage message)
|
||||
{
|
||||
message.Reply(_requestEditor!.Text);
|
||||
}
|
||||
}
|
||||
14
Needlework.Net.Desktop/Views/EndpointsContainerView.axaml
Normal file
14
Needlework.Net.Desktop/Views/EndpointsContainerView.axaml
Normal file
@@ -0,0 +1,14 @@
|
||||
<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>
|
||||
11
Needlework.Net.Desktop/Views/EndpointsContainerView.axaml.cs
Normal file
11
Needlework.Net.Desktop/Views/EndpointsContainerView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
|
||||
public partial class EndpointsContainerView : UserControl
|
||||
{
|
||||
public EndpointsContainerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,8 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.EndpointsView"
|
||||
x:DataType="vm:EndpointsViewModel">
|
||||
<!-- TOP LEVEL -->
|
||||
<suki:BusyArea IsBusy="{Binding IsBusy}" BusyText="Loading...">
|
||||
<Grid Margin="16" RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||
<TextBlock Classes="h3" Margin="0 4" Grid.Row="0" Grid.Column="0">Endpoints</TextBlock>
|
||||
<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}">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
<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"
|
||||
@@ -10,8 +11,15 @@
|
||||
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 -->
|
||||
@@ -40,13 +48,25 @@
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
<Button Classes="Flat"
|
||||
Content="{Binding Version}" />
|
||||
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.">
|
||||
<materialIcons:MaterialIcon Kind="Github" />
|
||||
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" />
|
||||
@@ -54,4 +74,4 @@
|
||||
</StackPanel>
|
||||
</suki:SukiSideMenu.FooterContent>
|
||||
</suki:SukiSideMenu>
|
||||
</Window>
|
||||
</suki:SukiWindow>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Avalonia.Controls;
|
||||
using SukiUI.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
|
||||
45
Needlework.Net.Desktop/Views/OopsiesWindow.axaml
Normal file
45
Needlework.Net.Desktop/Views/OopsiesWindow.axaml
Normal file
@@ -0,0 +1,45 @@
|
||||
<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>
|
||||
11
Needlework.Net.Desktop/Views/OopsiesWindow.axaml.cs
Normal file
11
Needlework.Net.Desktop/Views/OopsiesWindow.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using SukiUI.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
|
||||
public partial class OopsiesWindow : SukiWindow
|
||||
{
|
||||
public OopsiesWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +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:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Desktop.Views.PluginView"
|
||||
x:DataType="vm:PluginViewModel">
|
||||
<!-- TOP LEVEL -->
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<suki:GlassCard>
|
||||
<StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</suki:GlassCard>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -1,12 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views
|
||||
{
|
||||
public partial class PluginView : UserControl
|
||||
{
|
||||
public PluginView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Needlework.Net.Desktop/Views/WebsocketView.axaml
Normal file
68
Needlework.Net.Desktop/Views/WebsocketView.axaml
Normal file
@@ -0,0 +1,68 @@
|
||||
<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"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
Text=""
|
||||
FontSize="14"/>
|
||||
</suki:GlassCard>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
57
Needlework.Net.Desktop/Views/WebsocketView.axaml.cs
Normal file
57
Needlework.Net.Desktop/Views/WebsocketView.axaml.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
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 TextMateSharp.Grammars;
|
||||
|
||||
namespace Needlework.Net.Desktop.Views;
|
||||
|
||||
public partial class WebsocketView : UserControl, IRecipient<ResponseUpdatedMessage>
|
||||
{
|
||||
private TextEditor? _responseEditor;
|
||||
|
||||
public WebsocketView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Receive(ResponseUpdatedMessage message)
|
||||
{
|
||||
_responseEditor!.Text = message.Value;
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
var vm = (WebsocketViewModel)DataContext!;
|
||||
var viewer = this.FindControl<ListBox>("EventViewer");
|
||||
viewer!.PropertyChanged += (s, e) => { if (vm.IsTail) viewer.ScrollIntoView(vm.EventLog.Count - 1); };
|
||||
|
||||
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
||||
_responseEditor?.ApplyJsonEditorSettings();
|
||||
|
||||
WeakReferenceMessenger.Default.Register(this, nameof(WebsocketViewModel));
|
||||
|
||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||
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));
|
||||
}
|
||||
}
|
||||
BIN
Needlework.Net.Desktop/app.ico
Normal file
BIN
Needlework.Net.Desktop/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
Reference in New Issue
Block a user