mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 10:10:48 +01:00
feat: add schema search pane
This commit is contained in:
243
Needlework.Net/Helpers/OpenApiHelpers.cs
Normal file
243
Needlework.Net/Helpers/OpenApiHelpers.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net.Helpers
|
||||
{
|
||||
public static class OpenApiHelpers
|
||||
{
|
||||
public static string GetReturnType(OpenApiResponses responses)
|
||||
{
|
||||
if (!TryGetResponse(responses, out var response))
|
||||
return "none";
|
||||
|
||||
if (response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
|
||||
return "none";
|
||||
}
|
||||
|
||||
public static string GetSchemaType(OpenApiSchema? schema)
|
||||
{
|
||||
if (schema == null) return "object"; // Because GetLolVanguardV1Notification exists where it has a required parameter without a type...
|
||||
if (schema.Reference != null) return schema.Reference.Id;
|
||||
if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||
if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}";
|
||||
if (schema.Type == "array" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||
if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]";
|
||||
if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]";
|
||||
if (schema.Type == "array") return $"{schema.Items.Type}[]";
|
||||
return schema.Type;
|
||||
}
|
||||
|
||||
public static List<string> CreateTemplate(List<PropertyClassViewModel> requestClasses)
|
||||
{
|
||||
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
|
||||
{
|
||||
List<PropertyClassViewModel> classes = [.. requestClasses];
|
||||
classes.Remove(rootClass);
|
||||
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
template[i] = GetRequestDefaultValue(type);
|
||||
}
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
public static string GetComponentId(OpenApiSchema schema)
|
||||
{
|
||||
string componentId;
|
||||
if (schema.Reference != null) componentId = schema.Reference.Id;
|
||||
else if (schema.Items != null) componentId = schema.Items.Reference.Id;
|
||||
else componentId = schema.AdditionalProperties.Reference.Id;
|
||||
return componentId;
|
||||
}
|
||||
|
||||
public static List<ParameterViewModel> GetParameters(List<OpenApiParameter> parameters, ParameterLocation location)
|
||||
{
|
||||
var pathParameters = new List<ParameterViewModel>();
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
if (parameter.In != location) continue;
|
||||
pathParameters.Add(new ParameterViewModel(parameter.Name, GetSchemaType(parameter.Schema), parameter.Required));
|
||||
}
|
||||
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
public static string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
||||
{
|
||||
if (requestBody == null) return null;
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return null; // Because "PostLolAccountVerificationV1SendDeactivationPin" exists where the media body is empty...
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document document)
|
||||
{
|
||||
if (requestBody == null) return [];
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var rawDocument = document.OpenApiDocument;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
var type = GetSchemaType(media.Schema);
|
||||
if (IsComponent(type))
|
||||
{
|
||||
var componentId = GetComponentId(schema);
|
||||
var componentSchema = rawDocument.Components.Schemas[componentId];
|
||||
List<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(componentSchema, propertyClasses, rawDocument);
|
||||
return propertyClasses;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public static string GetRequestDefaultValue(string type)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public static string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document)
|
||||
{
|
||||
var requestClasses = GetRequestClasses(requestBody, document);
|
||||
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);
|
||||
}
|
||||
|
||||
public static List<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document document)
|
||||
{
|
||||
if (!TryGetResponse(responses, out var response))
|
||||
return [];
|
||||
|
||||
if (response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var rawDocument = document.OpenApiDocument;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
List<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(schema, propertyClasses, rawDocument);
|
||||
return propertyClasses;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public static bool IsComponent(string type)
|
||||
{
|
||||
return !(type.Contains("object")
|
||||
|| type.Contains("array")
|
||||
|| type.Contains("bool")
|
||||
|| type.Contains("string")
|
||||
|| type.Contains("integer")
|
||||
|| type.Contains("number"));
|
||||
}
|
||||
|
||||
public static bool TryGetResponse(OpenApiResponses responses, [NotNullWhen(true)] out OpenApiResponse? response)
|
||||
{
|
||||
response = null;
|
||||
var flag = false;
|
||||
if (responses.TryGetValue("2XX", out var x))
|
||||
{
|
||||
response = x;
|
||||
flag = true;
|
||||
}
|
||||
else if (responses.TryGetValue("200", out var y))
|
||||
{
|
||||
response = y;
|
||||
flag = true;
|
||||
}
|
||||
return flag;
|
||||
|
||||
}
|
||||
|
||||
public static void WalkSchema(OpenApiSchema schema, List<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||
{
|
||||
var type = GetSchemaType(schema);
|
||||
if (IsComponent(type))
|
||||
{
|
||||
string componentId = GetComponentId(schema);
|
||||
var componentSchema = document.Components.Schemas[componentId];
|
||||
var responseClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
||||
|
||||
if (propertyClasses.Where(c => c.Id == componentId).Any()) return; // Avoid adding duplicate schemas in classes
|
||||
propertyClasses.Add(responseClass);
|
||||
|
||||
foreach ((var _, var property) in componentSchema.Properties)
|
||||
// Check for self-references like "LolLootLootOddsResponse"
|
||||
// I blame dubble
|
||||
if (IsComponent(GetSchemaType(property)) && componentId != GetComponentId(property))
|
||||
WalkSchema(property, propertyClasses, document);
|
||||
}
|
||||
}
|
||||
|
||||
public static PropertyClassViewModel WalkSchema(OpenApiSchema schema, OpenApiDocument document)
|
||||
{
|
||||
string componentId = GetComponentId(schema);
|
||||
var componentSchema = document.Components.Schemas[componentId];
|
||||
var propertyClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
||||
return propertyClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.8" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.1" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.8" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.8" />
|
||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.3.0" />
|
||||
<PackageReference Include="BlossomiShymae.Briar" Version="0.2.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.OpenApi.Readers;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Models;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net
|
||||
@@ -20,7 +21,7 @@ namespace Needlework.Net
|
||||
_githubUserContentClient = clients.Get("GithubUserContentClient");
|
||||
}
|
||||
|
||||
public async Task<Document> GetLcuSchemaDocumentAsync()
|
||||
public async Task<Document> GetLcuSchemaDocumentAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (Cached<Document>.TryGet(nameof(GetLcuSchemaDocumentAsync), out var cached))
|
||||
{
|
||||
@@ -28,14 +29,14 @@ namespace Needlework.Net
|
||||
}
|
||||
|
||||
var lcuSchemaStream = await _githubUserContentClient.Request("/dysolix/hasagi-types/main/swagger.json")
|
||||
.GetStreamAsync();
|
||||
.GetStreamAsync(cancellationToken: cancellationToken);
|
||||
var lcuSchemaRaw = _reader.Read(lcuSchemaStream, out var _);
|
||||
var document = new Document(lcuSchemaRaw);
|
||||
|
||||
return cached.Save(document, TimeSpan.FromMinutes(60));
|
||||
}
|
||||
|
||||
public async Task<Document> GetLolClientDocumentAsync()
|
||||
public async Task<Document> GetLolClientDocumentAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (Cached<Document>.TryGet(nameof(GetLolClientDocumentAsync), out var cached))
|
||||
{
|
||||
@@ -43,7 +44,7 @@ namespace Needlework.Net
|
||||
}
|
||||
|
||||
var lolClientStream = await _githubUserContentClient.Request("/AlsoSylv/Irelia/refs/heads/master/schemas/game_schema.json")
|
||||
.GetStreamAsync();
|
||||
.GetStreamAsync(cancellationToken: cancellationToken);
|
||||
var lolClientRaw = _reader.Read(lolClientStream, out var _);
|
||||
var document = new Document(lolClientRaw);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using BlossomiShymae.Briar;
|
||||
using BlossomiShymae.Briar.Utils;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
@@ -9,6 +10,7 @@ using FluentAvalonia.UI.Controls;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Needlework.Net.Extensions;
|
||||
using Needlework.Net.Helpers;
|
||||
using Needlework.Net.Messages;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Services;
|
||||
@@ -23,6 +25,7 @@ using System.Net.Http.Json;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow;
|
||||
@@ -104,6 +107,15 @@ public partial class MainWindowViewModel
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isPaneOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<SchemaViewModel> _schemas = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private SchemaViewModel? _selectedSchema;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<NotificationViewModel> _notifications = [];
|
||||
|
||||
@@ -113,6 +125,9 @@ public partial class MainWindowViewModel
|
||||
[ObservableProperty]
|
||||
private PageBase _currentPage;
|
||||
|
||||
[ObservableProperty]
|
||||
private SchemaSearchDetailsViewModel? _selectedSchemaSearchDetails;
|
||||
|
||||
public List<NavigationViewItem> NavigationViewItems { get; private set; } = [];
|
||||
|
||||
public bool IsSchemaVersionChecked { get; private set; }
|
||||
@@ -121,6 +136,54 @@ public partial class MainWindowViewModel
|
||||
|
||||
public string Title => $"Needlework.Net {Version}";
|
||||
|
||||
partial void OnSelectedNavigationViewItemChanged(NavigationViewItem value)
|
||||
{
|
||||
if (value.Tag is PageBase page)
|
||||
{
|
||||
CurrentPage = page;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedSchemaSearchDetailsChanged(SchemaSearchDetailsViewModel? value)
|
||||
{
|
||||
if (value == null) return;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var document = value.Tab switch
|
||||
{
|
||||
Pages.Endpoints.Tab.LCU => await _documentService.GetLcuSchemaDocumentAsync(),
|
||||
Pages.Endpoints.Tab.GameClient => await _documentService.GetLolClientDocumentAsync(),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
var propertyClassViewModel = OpenApiHelpers.WalkSchema(document.OpenApiDocument.Components.Schemas[value.Key], document.OpenApiDocument);
|
||||
var schemaViewModel = new SchemaViewModel(propertyClassViewModel);
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (Schemas.ToList().Find(schema => schema.Id == schemaViewModel.Id) == null)
|
||||
{
|
||||
Schemas.Add(schemaViewModel);
|
||||
IsPaneOpen = true;
|
||||
|
||||
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
partial void OnSelectedSchemaChanged(SchemaViewModel? value)
|
||||
{
|
||||
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
partial void OnSchemasChanged(ObservableCollection<SchemaViewModel> value)
|
||||
{
|
||||
if (!value.Any())
|
||||
{
|
||||
IsPaneOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private NavigationViewItem ToNavigationViewItem(PageBase page) => new()
|
||||
{
|
||||
Content = page.DisplayName,
|
||||
@@ -140,14 +203,6 @@ public partial class MainWindowViewModel
|
||||
}
|
||||
};
|
||||
|
||||
partial void OnSelectedNavigationViewItemChanged(NavigationViewItem value)
|
||||
{
|
||||
if (value.Tag is PageBase page)
|
||||
{
|
||||
CurrentPage = page;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckForUpdatesAsync()
|
||||
{
|
||||
var release = await _githubClient
|
||||
@@ -194,6 +249,66 @@ public partial class MainWindowViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<object>> PopulateAsync(string? searchText, CancellationToken cancellationToken)
|
||||
{
|
||||
if (searchText == null) return [];
|
||||
|
||||
var lcuSchemaDocument = await _documentService.GetLcuSchemaDocumentAsync(cancellationToken);
|
||||
var gameClientDocument = await _documentService.GetLolClientDocumentAsync(cancellationToken);
|
||||
var lcuResults = lcuSchemaDocument.OpenApiDocument.Components.Schemas.Keys.Where(key => key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(key => new SchemaSearchDetailsViewModel(key, Pages.Endpoints.Tab.LCU));
|
||||
var gameClientResults = gameClientDocument.OpenApiDocument.Components.Schemas.Keys.Where(key => key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(key => new SchemaSearchDetailsViewModel(key, Pages.Endpoints.Tab.GameClient));
|
||||
|
||||
return Enumerable.Concat(lcuResults, gameClientResults);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanOpenSchemaPane))]
|
||||
private void OpenSchemaPane()
|
||||
{
|
||||
IsPaneOpen = !IsPaneOpen;
|
||||
}
|
||||
|
||||
private bool CanOpenSchemaPane()
|
||||
{
|
||||
return Schemas.Any();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanCloseSchema))]
|
||||
private void CloseSchema()
|
||||
{
|
||||
if (SelectedSchema is SchemaViewModel selection)
|
||||
{
|
||||
SelectedSchema = null;
|
||||
Schemas = new ObservableCollection<SchemaViewModel>(Schemas.Where(schema => schema != selection));
|
||||
|
||||
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanCloseSchema()
|
||||
{
|
||||
return SelectedSchema != null;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanCloseSchemaAll))]
|
||||
private void CloseSchemaAll()
|
||||
{
|
||||
SelectedSchema = null;
|
||||
Schemas = [];
|
||||
|
||||
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool CanCloseSchemaAll()
|
||||
{
|
||||
return Schemas.Any();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow
|
||||
{
|
||||
public partial class SchemaSearchDetailsViewModel : ObservableObject
|
||||
{
|
||||
public SchemaSearchDetailsViewModel(string key, Tab tab)
|
||||
{
|
||||
Tab = tab;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
|
||||
public Tab Tab { get; }
|
||||
|
||||
public string Document => Tab switch
|
||||
{
|
||||
Tab.LCU => "LCU",
|
||||
Tab.GameClient => "Game Client",
|
||||
_ => throw new System.NotImplementedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
22
Needlework.Net/ViewModels/MainWindow/SchemaViewModel.cs
Normal file
22
Needlework.Net/ViewModels/MainWindow/SchemaViewModel.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Needlework.Net.ViewModels.MainWindow
|
||||
{
|
||||
public partial class SchemaViewModel : ObservableObject
|
||||
{
|
||||
public SchemaViewModel(PropertyClassViewModel vm)
|
||||
{
|
||||
Id = vm.Id;
|
||||
PropertyFields = vm.PropertyFields;
|
||||
PropertyEnums = vm.PropertyEnums;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public List<PropertyFieldViewModel> PropertyFields { get; } = [];
|
||||
|
||||
public List<PropertyEnumViewModel> PropertyEnums { get; } = [];
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Models;
|
||||
using Needlework.Net.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||
|
||||
@@ -15,13 +13,13 @@ public partial class OperationViewModel : ObservableObject
|
||||
Summary = operation.Summary ?? string.Empty;
|
||||
Description = operation.Description ?? string.Empty;
|
||||
IsRequestBody = operation.RequestBody != null;
|
||||
ReturnType = GetReturnType(operation.Responses);
|
||||
RequestClasses = GetRequestClasses(operation.RequestBody, document);
|
||||
ResponseClasses = GetResponseClasses(operation.Responses, document);
|
||||
PathParameters = GetParameters(operation.Parameters.ToList(), ParameterLocation.Path);
|
||||
QueryParameters = GetParameters(operation.Parameters.ToList(), ParameterLocation.Query);
|
||||
RequestBodyType = GetRequestBodyType(operation.RequestBody);
|
||||
RequestTemplate = GetRequestTemplate(operation.RequestBody, document);
|
||||
ReturnType = OpenApiHelpers.GetReturnType(operation.Responses);
|
||||
RequestClasses = OpenApiHelpers.GetRequestClasses(operation.RequestBody, document);
|
||||
ResponseClasses = OpenApiHelpers.GetResponseClasses(operation.Responses, document);
|
||||
PathParameters = OpenApiHelpers.GetParameters(operation.Parameters.ToList(), ParameterLocation.Path);
|
||||
QueryParameters = OpenApiHelpers.GetParameters(operation.Parameters.ToList(), ParameterLocation.Query);
|
||||
RequestBodyType = OpenApiHelpers.GetRequestBodyType(operation.RequestBody);
|
||||
RequestTemplate = OpenApiHelpers.GetRequestTemplate(operation.RequestBody, document);
|
||||
}
|
||||
|
||||
public string Summary { get; }
|
||||
@@ -43,226 +41,4 @@ public partial class OperationViewModel : ObservableObject
|
||||
public List<ParameterViewModel> QueryParameters { get; }
|
||||
|
||||
public string? RequestTemplate { get; }
|
||||
|
||||
private string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document)
|
||||
{
|
||||
var requestClasses = GetRequestClasses(requestBody, document);
|
||||
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(List<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
|
||||
{
|
||||
List<PropertyClassViewModel> classes = [.. requestClasses];
|
||||
classes.Remove(rootClass);
|
||||
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
template[i] = GetRequestDefaultValue(type);
|
||||
}
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
private static string GetRequestDefaultValue(string type)
|
||||
{
|
||||
var defaultValue = string.Empty;
|
||||
if (type.Contains("[]")) defaultValue = "[]";
|
||||
else if (type.Contains("string")) defaultValue = "\"\"";
|
||||
else if (type.Contains("boolean")) defaultValue = "false";
|
||||
else if (type.Contains("integer")) defaultValue = "0";
|
||||
else if (type.Contains("double") || type.Contains("float")) defaultValue = "0.0";
|
||||
else if (type.Contains("object")) defaultValue = "{}";
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
||||
{
|
||||
if (requestBody == null) return null;
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return null; // Because "PostLolAccountVerificationV1SendDeactivationPin" exists where the media body is empty...
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<ParameterViewModel> GetParameters(List<OpenApiParameter> parameters, ParameterLocation location)
|
||||
{
|
||||
var pathParameters = new List<ParameterViewModel>();
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
if (parameter.In != location) continue;
|
||||
pathParameters.Add(new ParameterViewModel(parameter.Name, GetSchemaType(parameter.Schema), parameter.Required));
|
||||
}
|
||||
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
private bool TryGetResponse(OpenApiResponses responses, [NotNullWhen(true)] out OpenApiResponse? response)
|
||||
{
|
||||
response = null;
|
||||
var flag = false;
|
||||
if (responses.TryGetValue("2XX", out var x))
|
||||
{
|
||||
response = x;
|
||||
flag = true;
|
||||
}
|
||||
else if (responses.TryGetValue("200", out var y))
|
||||
{
|
||||
response = y;
|
||||
flag = true;
|
||||
}
|
||||
return flag;
|
||||
|
||||
}
|
||||
|
||||
private List<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document document)
|
||||
{
|
||||
if (!TryGetResponse(responses, out var response))
|
||||
return [];
|
||||
|
||||
if (response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var rawDocument = document.OpenApiDocument;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
List<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(schema, propertyClasses, rawDocument);
|
||||
return propertyClasses;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private void WalkSchema(OpenApiSchema schema, List<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||
{
|
||||
var type = GetSchemaType(schema);
|
||||
if (IsComponent(type))
|
||||
{
|
||||
string componentId = GetComponentId(schema);
|
||||
var componentSchema = document.Components.Schemas[componentId];
|
||||
var responseClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
||||
|
||||
if (propertyClasses.Where(c => c.Id == componentId).Any()) return; // Avoid adding duplicate schemas in classes
|
||||
propertyClasses.Add(responseClass);
|
||||
|
||||
foreach ((var _, var property) in componentSchema.Properties)
|
||||
// Check for self-references like "LolLootLootOddsResponse"
|
||||
// I blame dubble
|
||||
if (IsComponent(GetSchemaType(property)) && componentId != GetComponentId(property))
|
||||
WalkSchema(property, propertyClasses, document);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetComponentId(OpenApiSchema schema)
|
||||
{
|
||||
string componentId;
|
||||
if (schema.Reference != null) componentId = schema.Reference.Id;
|
||||
else if (schema.Items != null) componentId = schema.Items.Reference.Id;
|
||||
else componentId = schema.AdditionalProperties.Reference.Id;
|
||||
return componentId;
|
||||
}
|
||||
|
||||
private static bool IsComponent(string type)
|
||||
{
|
||||
return !(type.Contains("object")
|
||||
|| type.Contains("array")
|
||||
|| type.Contains("bool")
|
||||
|| type.Contains("string")
|
||||
|| type.Contains("integer")
|
||||
|| type.Contains("number"));
|
||||
}
|
||||
|
||||
private List<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document document)
|
||||
{
|
||||
if (requestBody == null) return [];
|
||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var rawDocument = document.OpenApiDocument;
|
||||
var schema = media.Schema;
|
||||
if (schema == null) return [];
|
||||
|
||||
var type = GetSchemaType(media.Schema);
|
||||
if (IsComponent(type))
|
||||
{
|
||||
var componentId = GetComponentId(schema);
|
||||
var componentSchema = rawDocument.Components.Schemas[componentId];
|
||||
List<PropertyClassViewModel> propertyClasses = [];
|
||||
WalkSchema(componentSchema, propertyClasses, rawDocument);
|
||||
return propertyClasses;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private string GetReturnType(OpenApiResponses responses)
|
||||
{
|
||||
if (!TryGetResponse(responses, out var response))
|
||||
return "none";
|
||||
|
||||
if (response.Content.TryGetValue("application/json", out var media))
|
||||
{
|
||||
var schema = media.Schema;
|
||||
return GetSchemaType(schema);
|
||||
}
|
||||
|
||||
return "none";
|
||||
}
|
||||
|
||||
public static string GetSchemaType(OpenApiSchema? schema)
|
||||
{
|
||||
if (schema == null) return "object"; // Because GetLolVanguardV1Notification exists where it has a required parameter without a type...
|
||||
if (schema.Reference != null) return schema.Reference.Id;
|
||||
if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||
if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}";
|
||||
if (schema.Type == "array" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||
if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]";
|
||||
if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]";
|
||||
if (schema.Type == "array") return $"{schema.Items.Type}[]";
|
||||
return schema.Type;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Needlework.Net.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -14,7 +15,7 @@ public class PropertyClassViewModel : ObservableObject
|
||||
List<PropertyEnumViewModel> propertyEnums = [];
|
||||
foreach ((var propertyName, var propertySchema) in properties)
|
||||
{
|
||||
var type = OperationViewModel.GetSchemaType(propertySchema);
|
||||
var type = OpenApiHelpers.GetSchemaType(propertySchema);
|
||||
var field = new PropertyFieldViewModel(propertyName, type);
|
||||
propertyFields.Add(field);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
Width="1280"
|
||||
Height="720">
|
||||
<Grid RowDefinitions="auto,*">
|
||||
<Grid ColumnDefinitions="auto,auto,*,auto"
|
||||
<Grid Name="TitleBarHost"
|
||||
ColumnDefinitions="auto,auto,*,auto"
|
||||
Background="Transparent"
|
||||
Height="40"
|
||||
Grid.Row="0">
|
||||
@@ -32,8 +33,38 @@
|
||||
IsHitTestVisible="False"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="1"/>
|
||||
<Border Grid.Column="2" Padding="4" IsHitTestVisible="True">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<AutoCompleteBox Name="SchemaAutoCompleteBox"
|
||||
Margin="0 0 8 0"
|
||||
MinWidth="200"
|
||||
MaxWidth="500"
|
||||
MaxDropDownHeight="400"
|
||||
Watermark="Search schemas"
|
||||
FilterMode="None"
|
||||
ValueMemberBinding="{Binding Key, DataType=vm:SchemaSearchDetailsViewModel}"
|
||||
AsyncPopulator="{Binding PopulateAsync}"
|
||||
SelectedItem="{Binding SelectedSchemaSearchDetails}">
|
||||
<AutoCompleteBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SchemaSearchDetailsViewModel">
|
||||
<ContentControl Content="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</AutoCompleteBox.ItemTemplate>
|
||||
</AutoCompleteBox>
|
||||
<Button i:Attached.Icon="fa-solid fa-file-lines"
|
||||
FontSize="20"
|
||||
Command="{Binding OpenSchemaPaneCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<ui:NavigationView AlwaysShowHeader="False"
|
||||
<SplitView Grid.Row="1"
|
||||
PaneBackground="Transparent"
|
||||
IsPaneOpen="{Binding IsPaneOpen}"
|
||||
DisplayMode="Inline"
|
||||
OpenPaneLength="350"
|
||||
PanePlacement="Right">
|
||||
<ui:NavigationView AlwaysShowHeader="False"
|
||||
PaneDisplayMode="Left"
|
||||
IsSettingsVisible="False"
|
||||
IsPaneOpen="False"
|
||||
@@ -41,48 +72,76 @@
|
||||
Grid.Row="1"
|
||||
MenuItemsSource="{Binding NavigationViewItems}"
|
||||
SelectedItem="{Binding SelectedNavigationViewItem}">
|
||||
<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>
|
||||
<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}"/>
|
||||
<ItemsControl ItemsSource="{Binding Notifications}"
|
||||
VerticalAlignment="Bottom">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:NotificationViewModel">
|
||||
<ContentControl Content="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ui:NavigationView>
|
||||
<SplitView.Pane>
|
||||
<StackPanel>
|
||||
<ui:CommandBar DefaultLabelPosition="Right">
|
||||
<ui:CommandBar.PrimaryCommands>
|
||||
<ui:CommandBarButton Name="CloseCommandBarButton" Label="Close" Command="{Binding CloseSchemaCommand}"/>
|
||||
<ui:CommandBarButton Name="CloseAllCommandBarButton" Label="Close all" Command="{Binding CloseSchemaAllCommand}"/>
|
||||
</ui:CommandBar.PrimaryCommands>
|
||||
</ui:CommandBar>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
|
||||
Margin="8 0 8 0">
|
||||
<ListBox ItemsSource="{Binding Schemas}" SelectedItem="{Binding SelectedSchema}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
|
||||
<Setter Property="Padding" Value="0"></Setter>
|
||||
<Setter Property="Margin" Value="0 0 0 8"></Setter>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</ui:NavigationView.PaneFooter>
|
||||
<Grid>
|
||||
<TransitioningContentControl Content="{Binding CurrentPage}"/>
|
||||
<ItemsControl ItemsSource="{Binding Notifications}"
|
||||
VerticalAlignment="Bottom">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:NotificationViewModel">
|
||||
<ContentControl Content="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ui:NavigationView>
|
||||
</SplitView.Pane>
|
||||
</SplitView>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
using System;
|
||||
|
||||
namespace Needlework.Net.Views.MainWindow;
|
||||
|
||||
@@ -10,7 +15,35 @@ public partial class MainWindowView : AppWindow
|
||||
InitializeComponent();
|
||||
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;
|
||||
TransparencyLevelHint = [WindowTransparencyLevel.Mica, WindowTransparencyLevel.None];
|
||||
Background = IsWindows11 ? null : Background;
|
||||
|
||||
SchemaAutoCompleteBox.MinimumPopulateDelay = TimeSpan.FromSeconds(1);
|
||||
SchemaAutoCompleteBox.MinimumPrefixLength = 3;
|
||||
|
||||
CloseCommandBarButton.IconSource = new ImageIconSource
|
||||
{
|
||||
Source = new Projektanker.Icons.Avalonia.IconImage()
|
||||
{
|
||||
Value = "fa-solid fa-file-circle-xmark",
|
||||
Brush = new SolidColorBrush(Application.Current!.ActualThemeVariant.Key switch
|
||||
{
|
||||
"Light" => Colors.Black,
|
||||
"Dark" => Colors.White,
|
||||
_ => Colors.Gray
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
|
||||
if (VisualRoot is AppWindow aw)
|
||||
{
|
||||
TitleBarHost.ColumnDefinitions[3].Width = new GridLength(aw.TitleBar.RightInset, GridUnitType.Pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:Needlework.Net.ViewModels.MainWindow"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.MainWindow.SchemaSearchDetailsView"
|
||||
x:DataType="vm:SchemaSearchDetailsViewModel">
|
||||
<StackPanel HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Key}"/>
|
||||
<TextBlock Text="{Binding Document}"
|
||||
Theme="{StaticResource CaptionTextBlockStyle}"
|
||||
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Views.MainWindow;
|
||||
|
||||
public partial class SchemaSearchDetailsView : UserControl
|
||||
{
|
||||
public SchemaSearchDetailsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
42
Needlework.Net/Views/MainWindow/SchemaView.axaml
Normal file
42
Needlework.Net/Views/MainWindow/SchemaView.axaml
Normal file
@@ -0,0 +1,42 @@
|
||||
<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.MainWindow"
|
||||
xmlns:controls="using:Needlework.Net.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Needlework.Net.Views.MainWindow.SchemaView"
|
||||
x:DataType="vm:SchemaViewModel">
|
||||
<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>
|
||||
</UserControl.Styles>
|
||||
<controls:Card>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="12" FontWeight="DemiBold" Text="{Binding Id}" Margin="0 0 0 4"/>
|
||||
<DataGrid IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableToVisibilityConverter}}"
|
||||
ItemsSource="{Binding PropertyFields}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
<DataGrid IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableToVisibilityConverter}}"
|
||||
Margin="0 0 0 8"
|
||||
ItemsSource="{Binding PropertyEnums}"
|
||||
AutoGenerateColumns="True"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal">
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
</controls:Card>
|
||||
</UserControl>
|
||||
11
Needlework.Net/Views/MainWindow/SchemaView.axaml.cs
Normal file
11
Needlework.Net/Views/MainWindow/SchemaView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Needlework.Net.Views.MainWindow;
|
||||
|
||||
public partial class SchemaView : UserControl
|
||||
{
|
||||
public SchemaView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user