diff --git a/Api/KernelFunctions.cs b/Api/KernelFunctions.cs index 6773b08..4b5b001 100644 --- a/Api/KernelFunctions.cs +++ b/Api/KernelFunctions.cs @@ -43,10 +43,22 @@ public async Task 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; } }); diff --git a/Client/Shared/CodeCell.razor b/Client/Shared/CodeCell.razor index 1841063..bf16170 100644 --- a/Client/Shared/CodeCell.razor +++ b/Client/Shared/CodeCell.razor @@ -10,27 +10,8 @@ -
- @* TODO: Create a cleaner system for formatting different kinds of output/status *@ - @if (isEvaluating) - { - ... - } - else if (result != null) - { - if (!string.IsNullOrEmpty(result.CommandFailedMessage)) - { - @result.CommandFailedMessage - } - else if (result.Output != null) - { - @result.Output - } - else - { - (No output) - } - } +
+
@@ -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; @@ -84,4 +62,4 @@ return await response.Content.ReadFromJsonAsync(); } -} \ No newline at end of file +} diff --git a/Client/Shared/CodeCell.razor.css b/Client/Shared/CodeCell.razor.css index b62b6b4..9c8e925 100644 --- a/Client/Shared/CodeCell.razor.css +++ b/Client/Shared/CodeCell.razor.css @@ -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; diff --git a/Client/Shared/OutputDisplays/ErrorDisplay.razor b/Client/Shared/OutputDisplays/ErrorDisplay.razor new file mode 100644 index 0000000..8c4b46b --- /dev/null +++ b/Client/Shared/OutputDisplays/ErrorDisplay.razor @@ -0,0 +1,5 @@ +
@Result.OutputToString
+ +@code { + [Parameter] public ExecuteResult Result { get; set; } +} diff --git a/Client/Shared/OutputDisplays/ErrorDisplay.razor.css b/Client/Shared/OutputDisplays/ErrorDisplay.razor.css new file mode 100644 index 0000000..5a2a8d4 --- /dev/null +++ b/Client/Shared/OutputDisplays/ErrorDisplay.razor.css @@ -0,0 +1,6 @@ +.error { + color: white; + white-space: pre; + font-family: monospace; + --bgcol: red; +} diff --git a/Client/Shared/OutputDisplays/FallbackDisplay.razor b/Client/Shared/OutputDisplays/FallbackDisplay.razor new file mode 100644 index 0000000..7ee822e --- /dev/null +++ b/Client/Shared/OutputDisplays/FallbackDisplay.razor @@ -0,0 +1,16 @@ +@switch (OutputToString) +{ + case null: +
(no output)
+ break; + case "": +
(empty string)
+ break; + default: +
@OutputToString
+ break; +} + +@code { + [Parameter] public string OutputToString { get; set; } +} diff --git a/Client/Shared/OutputDisplays/OutputDisplay.razor b/Client/Shared/OutputDisplays/OutputDisplay.razor new file mode 100644 index 0000000..05a1826 --- /dev/null +++ b/Client/Shared/OutputDisplays/OutputDisplay.razor @@ -0,0 +1,43 @@ +@if (Result != null) +{ + if (Result.OutputType == "error") + { + + } + else + { + switch (_deserializedOutput) + { + default: + + 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; +} diff --git a/Client/_Imports.razor b/Client/_Imports.razor index 837fa2b..7e9db02 100644 --- a/Client/_Imports.razor +++ b/Client/_Imports.razor @@ -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 diff --git a/Shared/ExecuteResult.cs b/Shared/ExecuteResult.cs index ae4e173..9865cfc 100644 --- a/Shared/ExecuteResult.cs +++ b/Shared/ExecuteResult.cs @@ -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; } } }