Skip to content

Commit

Permalink
General mechanism for displaying different kinds of structured output
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS committed Oct 28, 2020
1 parent cf84a47 commit 9fa4465
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 35 deletions.
16 changes: 14 additions & 2 deletions Api/KernelFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,22 @@ public async Task<IActionResult> RunCode(
switch (x)
{
case DisplayEvent displayEvent:
result.Output = displayEvent.Value?.ToString();
var value = displayEvent.Value;
result.OutputType = value?.GetType().AssemblyQualifiedName;
result.OutputToString = value?.ToString();
try
{
result.OutputJson = System.Text.Json.JsonSerializer.Serialize(value);
}
catch
{
// If it's not serializable, the client will just use OutputToString
}
break;
case CommandFailed commandFailed:
result.CommandFailedMessage = commandFailed.Message;
result.OutputType = "error";
result.OutputJson = null;
result.OutputToString = commandFailed.Message;
break;
}
});
Expand Down
28 changes: 3 additions & 25 deletions Client/Shared/CodeCell.razor
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,8 @@
</div>
<CodeEditor @bind-Code="CellContent" OnCompletionsRequested="GetCompletionsAsync" />
</div>
<div class="output @ResultCssClass">
@* TODO: Create a cleaner system for formatting different kinds of output/status *@
@if (isEvaluating)
{
<text>...</text>
}
else if (result != null)
{
if (!string.IsNullOrEmpty(result.CommandFailedMessage))
{
@result.CommandFailedMessage
}
else if (result.Output != null)
{
@result.Output
}
else
{
<text>(No output)</text>
}
}
<div class="output @(isEvaluating ? "loading" : "")">
<OutputDisplay @key="result" Result="result" />
</div>
</div>

Expand All @@ -44,9 +25,6 @@
private ExecuteResult result;
private bool isEvaluating;

private string ResultCssClass
=> !string.IsNullOrEmpty(result?.CommandFailedMessage) ? "command-failed" : null;

private string CellContent
{
get => cell.Content;
Expand Down Expand Up @@ -84,4 +62,4 @@

return await response.Content.ReadFromJsonAsync<Suggestion[]>();
}
}
}
26 changes: 20 additions & 6 deletions Client/Shared/CodeCell.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,30 @@ input {
}

.output {
padding: 10px;
background-color: #eee;
min-height: 20px;
transition: filter 0.5s ease-in-out;
}
.output > ::deep div {
padding: 10px;
animation-name: yellowfade;
animation-duration: 1s;
background-color: var(--bgcol);
}

@keyframes yellowfade {
from {
background-color: #f5ea7f;
}

.output.command-failed {
background-color: red;
color: white;
white-space: pre;
font-family: monospace;
to {
background-color: var(--bgcol);
}
}

.loading {
filter: brightness(0.5);
}

.toolbar {
flex-grow: 0;
Expand Down
5 changes: 5 additions & 0 deletions Client/Shared/OutputDisplays/ErrorDisplay.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="error">@Result.OutputToString</div>

@code {
[Parameter] public ExecuteResult Result { get; set; }
}
6 changes: 6 additions & 0 deletions Client/Shared/OutputDisplays/ErrorDisplay.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.error {
color: white;
white-space: pre;
font-family: monospace;
--bgcol: red;
}
16 changes: 16 additions & 0 deletions Client/Shared/OutputDisplays/FallbackDisplay.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@switch (OutputToString)
{
case null:
<div>(no output)</div>
break;
case "":
<div>(empty string)</div>
break;
default:
<div>@OutputToString</div>
break;
}

@code {
[Parameter] public string OutputToString { get; set; }
}
43 changes: 43 additions & 0 deletions Client/Shared/OutputDisplays/OutputDisplay.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@if (Result != null)
{
if (Result.OutputType == "error")
{
<ErrorDisplay Result="Result" />
}
else
{
switch (_deserializedOutput)
{
default:
<FallbackDisplay OutputToString="@Result.OutputToString" />
break;
}
}
}

@code {
[Parameter] public ExecuteResult Result { get; set; }

private object _deserializedOutput;

protected override void OnParametersSet()
{
if (Result?.OutputJson != null && Result.OutputType != "error")
{
try
{
var type = Type.GetType(Result.OutputType);
_deserializedOutput = System.Text.Json.JsonSerializer.Deserialize(Result.OutputJson, type);
}
catch
{
// If we can't deserialize it, we'll just use the string fallback
}
}
}

// We create a new instance of this component for each new ExecuteResult,
// so there's no need for existing instances to re-render
protected override bool ShouldRender()
=> false;
}
1 change: 1 addition & 0 deletions Client/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@using Microsoft.JSInterop
@using blazoract
@using blazoract.Client.Shared
@using blazoract.Client.Shared.OutputDisplays
@using blazoract.Shared
@using System.Threading
@using blazoract.Client.Data
Expand Down
7 changes: 5 additions & 2 deletions Shared/ExecuteResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
{
public class ExecuteResult
{
public object Output { get; set; }
// Tell the client which kind of object we're sending, so it can try to JSON-deserialize it as that type
public string OutputType { get; set; }
public string OutputJson { get; set; }

public string CommandFailedMessage { get; set; }
// In case the client can't deserialize this type (or the server can't serialize it), fall back on a string representation
public string OutputToString { get; set; }
}
}

0 comments on commit 9fa4465

Please sign in to comment.