Enhance OpenAPI UI components and styling

This commit is contained in:
Alexander Shabarshov 2025-04-08 15:24:28 +01:00
parent c1cac17c6b
commit 77ed16b75f
8 changed files with 52 additions and 18 deletions

View File

@ -42,6 +42,5 @@
[Parameter] [Parameter]
public OpenApiComponents? Value { get; set; } public OpenApiComponents? Value { get; set; }
private IList<OpenApiParameter> ToList(OpenApiParameter val) private IList<OpenApiParameter> ToList(OpenApiParameter val) => new List<OpenApiParameter>() { val };
=> new List<OpenApiParameter>() { val };
} }

View File

@ -1,8 +1,8 @@
<CascadingValue Name="OpenAPIUI_TOC_Parent" Value="_anchor"> <CascadingValue Name="OpenAPIUI_TOC_Parent" Value="_anchor">
<div id="@_anchor" class="expander @Class"> <div id="@_anchor" class="expander @Class">
<div class="ex-header @HeaderClass"> <div class="ex-header @HeaderClass" onclick="@(() => Toggle())">
@Title @Title
<div onclick="@(() => Toggle())"> <div>
@if (Collapsed) @if (Collapsed)
{ {
<svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@ -54,9 +54,9 @@
var example = GenerateExampleData(); var example = GenerateExampleData();
if (!string.IsNullOrWhiteSpace(example)) if (!string.IsNullOrWhiteSpace(example))
{ {
<div class="example-data"> <div class="example">
<h3>Example Data</h3> <h3 class="e-title">Example Data</h3>
<pre>@GenerateExampleData()</pre> <pre class="e-item">@GenerateExampleData()</pre>
</div> </div>
} }
@ -133,7 +133,9 @@
} }
else else
{ {
exampleData[content.Key] = GenerateExampleFromSchema(content.Value.Schema); var ex = GenerateExampleFromSchema(content.Value.Schema);
if ( ex != null )
exampleData[content.Key] = ex;
} }
} }
} }
@ -144,32 +146,39 @@
; ;
} }
private object GenerateExampleFromSchema(OpenApiSchema? schema) private object? GenerateExampleFromSchema(OpenApiSchema? schema)
{ {
if (schema == null) if (schema == null)
{ {
return new object(); return null;
} }
if (schema.Type == "object" && schema.Properties != null) if (schema.Type == "object" && schema.Properties != null)
{ {
var obj = new Dictionary<string, object>(); var obj = new Dictionary<string, object?>();
foreach (var property in schema.Properties) foreach (var property in schema.Properties)
{ {
obj[property.Key] = GenerateExampleFromSchema(property.Value); var ex = GenerateExampleFromSchema(property.Value);
if (ex != null)
obj[property.Key] = ex;
} }
if ( obj.Count == 0 )
return null;
return obj; return obj;
} }
if (schema.Type == "array" && schema.Items != null) if (schema.Type == "array" && schema.Items != null)
{ {
return new[] { GenerateExampleFromSchema(schema.Items) }; var ex = GenerateExampleFromSchema(schema.Items);
if (ex == null)
return null;
return new[] { ex };
} }
return schema.Default ?? schema.Example ?? GetDefaultValueForType(schema.Type); return schema.Default ?? schema.Example ?? GetDefaultValueForType(schema.Type);
} }
private object GetDefaultValueForType(string? type) private object? GetDefaultValueForType(string? type)
{ {
return type switch return type switch
{ {
@ -177,7 +186,7 @@
"integer" => 0, "integer" => 0,
"number" => 0.0, "number" => 0.0,
"boolean" => false, "boolean" => false,
_ => new object() _ => null
}; };
} }
} }

View File

@ -2,7 +2,7 @@
@implements IDisposable @implements IDisposable
<div class="@Class"> <div class="@Class">
<a @onclick="(async() => await ScrolltoAnchor())">@Title</a> <a class="toc-text" @onclick="(async() => await ScrolltoAnchor())">@Title</a>
@foreach (var child in Tree.GetChildren(Anchor)) @foreach (var child in Tree.GetChildren(Anchor))
{ {
<TableOfContents Title="@child.Name" Anchor="@child.Anchor" class="toc-level" Tree="@Tree"/> <TableOfContents Title="@child.Name" Anchor="@child.Anchor" class="toc-level" Tree="@Tree"/>

View File

@ -20,6 +20,7 @@ public interface ITableOfContentsTree
{ {
public event EventHandler Changed; public event EventHandler Changed;
void Clear();
void Add(string name, string anchor, string parentAnchor, bool collapsed); void Add(string name, string anchor, string parentAnchor, bool collapsed);
void Collapse(string anchor, bool collapsed); void Collapse(string anchor, bool collapsed);
bool IsCollapsed(string anchor); bool IsCollapsed(string anchor);

View File

@ -13,7 +13,9 @@
<CascadingValue Value="@_api" IsFixed="true"> <CascadingValue Value="@_api" IsFixed="true">
<CascadingValue Name="OpenAPIUI_TOC" Value="@_tree" IsFixed="true"> <CascadingValue Name="OpenAPIUI_TOC" Value="@_tree" IsFixed="true">
<Expander Title="Contents" Collapsed="true" HeaderClass="h1">
<TableOfContents Tree="@_tree" /> <TableOfContents Tree="@_tree" />
</Expander>
<HeaderControl Value="@_api.Info" DownloadUrl="@Url" /> <HeaderControl Value="@_api.Info" DownloadUrl="@Url" />
<ServersControl Value="@_api.Servers" /> <ServersControl Value="@_api.Servers" />
@ -70,6 +72,9 @@
if (loadedFromUrl || loadedFromJson) if (loadedFromUrl || loadedFromJson)
return; return;
_tree.Clear();
if (!string.IsNullOrWhiteSpace(Json)) if (!string.IsNullOrWhiteSpace(Json))
LoadFromJson(); LoadFromJson();
else if (!string.IsNullOrWhiteSpace(Url)) else if (!string.IsNullOrWhiteSpace(Url))

View File

@ -11,6 +11,12 @@ internal class TableOfContentsTree : ITableOfContentsTree
public event EventHandler Changed; public event EventHandler Changed;
public void Clear()
{
_nodes.Clear();
_order.Clear();
}
public void Add(string name, string anchor, string parentAnchor, bool collapsed) public void Add(string name, string anchor, string parentAnchor, bool collapsed)
{ {
if (_nodes.ContainsKey(anchor)) if (_nodes.ContainsKey(anchor))

View File

@ -2,11 +2,23 @@
margin-left: 20px; margin-left: 20px;
} }
.toc-text {
cursor: pointer;
}
.toc-text:hover {
text-decoration: underline !important;
}
.descriminator { .descriminator {
} }
.example {} .example {}
.e-item {} .e-item {
font-family: monospace;
font-size: var(--oa-font-small);
color: lightseagreen;
}
.e-name {} .e-name {}
.e-title {} .e-title {}
@ -18,6 +30,7 @@
.ex-header { .ex-header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
cursor: pointer;
} }
.header {} .header {}
@ -73,6 +86,7 @@
padding: 5px; padding: 5px;
border-radius: 5px; border-radius: 5px;
color: var(--oa-fg-lighter); color: var(--oa-fg-lighter);
cursor: pointer;
} }
.op-summary { .op-summary {
width: 100%; width: 100%;