Add tray rules and identities editor
PR and Push Build/Test / build-and-test (push) Successful in 7m0s

This commit is contained in:
2026-05-28 01:28:16 +02:00
parent 71f1e6a0b8
commit 693f52afee
31 changed files with 3136 additions and 327 deletions
@@ -0,0 +1,233 @@
#if WINDOWS
using Aprillz.MewUI;
using Aprillz.MewUI.Controls;
namespace MeetingAssistant.Workflow;
internal static class WorkflowRulesEditorMarkdownRenderer
{
public static UIElement CreateContent(string content)
{
var panel = new StackPanel()
.Vertical()
.Spacing(7);
foreach (var block in WorkflowRulesEditorMarkdown.Parse(content))
{
panel.Add(block switch
{
WorkflowRulesEditorMarkdownCodeBlock codeBlock => CreateCodeBlock(codeBlock),
WorkflowRulesEditorMarkdownTable table => CreateTable(table),
WorkflowRulesEditorMarkdownParagraph paragraph => CreateParagraph(paragraph),
_ => new TextBlock()
});
}
return panel;
}
private static UIElement CreateParagraph(WorkflowRulesEditorMarkdownParagraph paragraph)
{
var lines = SplitInlineLines(paragraph.Inlines);
if (lines.Count == 1)
{
return CreateParagraphLine(lines[0]);
}
var panel = new StackPanel()
.Vertical()
.Spacing(3);
foreach (var line in lines)
{
panel.Add(CreateParagraphLine(line));
}
return panel;
}
private static UIElement CreateParagraphLine(IReadOnlyList<WorkflowRulesEditorMarkdownInline> inlines)
{
var linePanel = new WrapPanel()
.Spacing(1);
foreach (var inline in inlines)
{
foreach (var element in CreateInlineElements(inline))
{
linePanel.Add(element);
}
}
return linePanel;
}
private static IEnumerable<UIElement> CreateInlineElements(WorkflowRulesEditorMarkdownInline inline)
{
if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Code)
{
yield return CreateInlineCode(inline.Text);
yield break;
}
foreach (var token in SplitInlineText(inline.Text))
{
var text = new TextBlock()
.Text(token)
.TextWrapping(TextWrapping.Wrap)
.Foreground(Color.FromRgb(230, 235, 243));
if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Bold)
{
text.FontWeight(FontWeight.Bold);
}
else if (inline.Style == WorkflowRulesEditorMarkdownInlineStyle.Italic)
{
text.FontFamily("Segoe UI Italic");
}
yield return text;
}
}
private static IReadOnlyList<IReadOnlyList<WorkflowRulesEditorMarkdownInline>> SplitInlineLines(
IReadOnlyList<WorkflowRulesEditorMarkdownInline> inlines)
{
var lines = new List<IReadOnlyList<WorkflowRulesEditorMarkdownInline>>();
var current = new List<WorkflowRulesEditorMarkdownInline>();
foreach (var inline in inlines)
{
var parts = inline.Text.Split('\n');
for (var index = 0; index < parts.Length; index++)
{
if (parts[index].Length > 0)
{
current.Add(inline with { Text = parts[index] });
}
if (index < parts.Length - 1)
{
lines.Add(current);
current = [];
}
}
}
lines.Add(current);
return lines;
}
private static UIElement CreateInlineCode(string text)
{
return new Border()
.Padding(4, 1, 4, 2)
.Margin(1, 0, 1, 0)
.CornerRadius(4)
.BorderThickness(1)
.BorderBrush(Color.FromRgb(86, 94, 108))
.Background(Color.FromRgb(24, 28, 35))
.Child(new TextBlock()
.Text(text)
.FontFamily("Consolas")
.Foreground(Color.FromRgb(237, 241, 247)));
}
private static UIElement CreateTable(WorkflowRulesEditorMarkdownTable table)
{
var rows = table.Rows
.Select(row => new MarkdownTableRow(PadCells(row, table.Header.Count)))
.ToArray();
var gridView = new GridView()
.HeaderHeight(30)
.RowHeight(30)
.CellPadding(new Thickness(8, 4, 8, 4))
.ShowGridLines(true)
.ZebraStriping(true);
for (var index = 0; index < table.Header.Count; index++)
{
var columnIndex = index;
gridView.AddColumn<MarkdownTableRow>(
table.Header[index],
GetMarkdownTableColumnWidth(table, index),
_ => new TextBlock()
.TextWrapping(TextWrapping.NoWrap)
.Foreground(Color.FromRgb(230, 235, 243)),
(element, row, _, _) => ((TextBlock)element).Text(row.GetCell(columnIndex)),
minWidth: 64,
resizable: true);
}
return gridView
.ItemsSource(rows)
.MinWidth(Math.Min(900, table.Header.Count * 120))
.Height(Math.Min(420, 30 + Math.Max(1, rows.Length) * 30));
}
private static UIElement CreateCodeBlock(WorkflowRulesEditorMarkdownCodeBlock codeBlock)
{
return new Border()
.Padding(9)
.CornerRadius(6)
.BorderThickness(1)
.BorderBrush(Color.FromRgb(86, 94, 108))
.Background(Color.FromRgb(20, 24, 31))
.Child(new TextBlock()
.Text(codeBlock.Text)
.TextWrapping(TextWrapping.Wrap)
.FontFamily("Consolas")
.Foreground(Color.FromRgb(237, 241, 247)));
}
private static IEnumerable<string> SplitInlineText(string text)
{
var start = 0;
while (start < text.Length)
{
var end = start;
var isWhitespace = char.IsWhiteSpace(text[start]);
while (end < text.Length && char.IsWhiteSpace(text[end]) == isWhitespace)
{
end++;
}
var token = text[start..end];
if (token.Length > 0)
{
yield return token;
}
start = end;
}
}
private static IReadOnlyList<string> PadCells(IReadOnlyList<string> cells, int count)
{
if (cells.Count >= count)
{
return cells;
}
return cells
.Concat(Enumerable.Repeat("", count - cells.Count))
.ToArray();
}
private static double GetMarkdownTableColumnWidth(
WorkflowRulesEditorMarkdownTable table,
int index)
{
var maxLength = new[] { table.Header[index].Length }
.Concat(table.Rows.Select(row => index < row.Count ? row[index].Length : 0))
.DefaultIfEmpty(0)
.Max();
return Math.Clamp(maxLength * 8 + 28, 72, 260);
}
private sealed record MarkdownTableRow(IReadOnlyList<string> Cells)
{
public string GetCell(int index)
{
return index >= 0 && index < Cells.Count
? Cells[index]
: "";
}
}
}
#endif