diff --git a/Cauldron.Macos/Extensions.cs b/Cauldron.Macos/Extensions.cs index ef6f045..ee90822 100644 --- a/Cauldron.Macos/Extensions.cs +++ b/Cauldron.Macos/Extensions.cs @@ -1,4 +1,8 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Html; @@ -14,4 +18,31 @@ public static class Extensions return writer.ToString(); } } + + public static string GetCSharpName(this Type type) + { + string name = type.Name; + + if (!type.IsGenericType) + return name; + + StringBuilder sb = new(); + + // Get just the name without the generic parameters + sb.Append(name[..name.IndexOf('`')]); + + // Add the generic parameters surrounded by < and > + sb.Append('<'); + sb.Append(type.GetGenericArguments() + .Select(t => t.GetCSharpName()) + .Join(", ")); + sb.Append('>'); + + return sb.ToString(); + } + + public static string Join(this IEnumerable strings, string separator) + { + return string.Join(separator, strings); + } } diff --git a/Cauldron.Macos/ScriptRunner.cs b/Cauldron.Macos/ScriptRunner.cs index 5fb128a..d262115 100644 --- a/Cauldron.Macos/ScriptRunner.cs +++ b/Cauldron.Macos/ScriptRunner.cs @@ -4,6 +4,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Rendering; using Cauldron.Core; +using System.Reflection; +using System.Linq; +using System.ComponentModel.DataAnnotations; +using System.Collections; namespace Cauldron.Macos; @@ -22,7 +26,11 @@ public static class ScriptRunner { window.BeginInvokeOnMainThread(() => { - body.InnerHtml.AppendHtml(GenerateValueOutput(obj)); + TagBuilder outputSection = new("section"); + outputSection.AddCssClass("output-section"); + outputSection.InnerHtml.AppendHtml(GenerateValueOutput(obj)); + body.InnerHtml.AppendHtml(outputSection); + window.ScriptOutputWebView.SetOutputPanelContent(body); }); @@ -54,34 +62,36 @@ public static class ScriptRunner }); } }, window.ScriptCancellationTokenSource.Token) - .ContinueWith((t) => window.SetScriptRunState(false), - uiThread); + .ContinueWith((t) => window.SetScriptRunState(false), uiThread); } private static TagBuilder GenerateValueOutput(object value) { - if (value is string str) + if (value is null) { TagBuilder tag = new("p"); - tag.InnerHtml.Append(str); + TagBuilder code = new("code"); + code.InnerHtml.Append("null"); + tag.InnerHtml.AppendHtml(code); return tag; } - if (value.GetType().IsPrimitive) + if (value.GetType().IsPrimitive || value is string) { TagBuilder tag = new("p"); tag.InnerHtml.Append(value.ToString()); return tag; } - if (value is IEnumerable enumberable) + if (value is IEnumerable enumerable) { - + return GenerateTable(enumerable); } - return null; + TagBuilder defaultTag = new("p"); + defaultTag.InnerHtml.Append(value.ToString()); + return defaultTag; } - private static void SetOutputPanelContent(this WebKit.WKWebView webView, - TagBuilder body) + private static void SetOutputPanelContent(this WebKit.WKWebView webView, TagBuilder body) { TagBuilder head = new("head"); TagBuilder style = new("style"); @@ -97,9 +107,117 @@ public static class ScriptRunner webView.LoadHtmlString(new Foundation.NSString(contents), null); } + private static TagBuilder GenerateTable(IEnumerable enumerable) + { + var listType = enumerable.GetType().GenericTypeArguments[0]; + + if (enumerable.GetType().GenericTypeArguments[0].IsPrimitive + || enumerable.GetType().GenericTypeArguments[0] == typeof(string)) + return GenerateSimpleTable(enumerable); + + TagBuilder output = new("table"); + + IList properties = enumerable.GetType() + .GenericTypeArguments[0].GetProperties() + .Where(p => !p.GetCustomAttributes(false) + .Any(a => a.GetAutoGenerateField() == false)) + .ToList(); + + // Caption + TagBuilder caption = new("caption"); + caption.InnerHtml.Append(enumerable.GetType().GetCSharpName()); + output.InnerHtml.AppendHtml(caption); + + // Header Row + List tableHeadCells = properties + .Select(p => + { + string displayName = p.GetCustomAttributes(false) + .Where(a => !string.IsNullOrEmpty(a.GetName())) + .FirstOrDefault() + ?.GetName() + ?? p.Name; + + TagBuilder th = new("th"); + th.InnerHtml.Append(displayName); + + return th; + }) + .ToList(); + + TagBuilder theadTr = new("tr"); + tableHeadCells.ForEach(th => theadTr.InnerHtml.AppendHtml(th)); + + TagBuilder thead = new("thead"); + thead.InnerHtml.AppendHtml(theadTr); + output.InnerHtml.AppendHtml(thead); + + // Content Rows + List tableRows = enumerable + .Select(item => + { + List cells = properties + .Select(p => + { + var content = p.GetValue(item); + + TagBuilder td = new("td"); + td.InnerHtml.AppendHtml(GenerateValueOutput(content)); + return td; + }) + .ToList(); + + TagBuilder tr = new("tr"); + cells.ForEach(td => tr.InnerHtml.AppendHtml(td)); + return tr; + }) + .ToList(); + + TagBuilder tbody = new("tbody"); + tableRows.ForEach(tr => tbody.InnerHtml.AppendHtml(tr)); + output.InnerHtml.AppendHtml(tbody); + + return output; + } + + private static TagBuilder GenerateSimpleTable(IEnumerable enumerable) + { + TagBuilder output = new("table"); + + // Caption + TagBuilder caption = new("caption"); + caption.InnerHtml.Append(enumerable.GetType().GetCSharpName()); + output.InnerHtml.AppendHtml(caption); + + // Content Rows + List tableRows = enumerable + .Select(item => + { + List cells = enumerable + .Select(v => + { + TagBuilder td = new("td"); + td.InnerHtml.AppendHtml(v.ToString()); + return td; + }) + .ToList(); + + TagBuilder tr = new("tr"); + cells.ForEach(td => tr.InnerHtml.AppendHtml(td)); + return tr; + }) + .ToList(); + + TagBuilder tbody = new("tbody"); + tableRows.ForEach(tr => tbody.InnerHtml.AppendHtml(tr)); + output.InnerHtml.AppendHtml(tbody); + + return output; + } + private const string outputCss = """ body { - font-family: -apple-system; + font-family: system-ui; } * {