diff --git a/app/MindWork AI Studio.sln b/app/MindWork AI Studio.sln index 0bb1ab52..ab62feb1 100644 --- a/app/MindWork AI Studio.sln +++ b/app/MindWork AI Studio.sln @@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build Script", "Build\Build EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedTools", "SharedTools\SharedTools.csproj", "{969C74DF-7678-4CD5-B269-D03E1ECA3D2A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGeneratedMappings", "SourceGeneratedMappings\SourceGeneratedMappings.csproj", "{4D7141D5-9C22-4D85-B748-290D15FF484C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,6 +32,10 @@ Global {969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU {969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU {969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Release|Any CPU.Build.0 = Release|Any CPU + {4D7141D5-9C22-4D85-B748-290D15FF484C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D7141D5-9C22-4D85-B748-290D15FF484C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D7141D5-9C22-4D85-B748-290D15FF484C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D7141D5-9C22-4D85-B748-290D15FF484C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution EndGlobalSection diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor index 4553ebc2..9e10140c 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor @@ -23,7 +23,7 @@ MaxLength="@textArea.MaxLength" Immediate="@textArea.IsImmediate" Adornment="@textArea.GetAdornmentPos()" - AdornmentIcon="@textArea.AdornmentIcon" + AdornmentIcon="@textArea.GetIconSvg()" AdornmentText="@textArea.AdornmentText" AdornmentColor="@textArea.GetAdornmentColor()" Variant="Variant.Outlined" diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index 0659eb96..8c9725e2 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -62,6 +62,19 @@ + + ..\SourceGeneratedMappings\SourceGeneratedMappings.csproj + ..\SourceGeneratedMappings\bin\$(Configuration)\net9.0\SourceGeneratedMappings.dll + + + + + + + + + + diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTextArea.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTextArea.cs index 09fb3b5b..dd4336c6 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTextArea.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTextArea.cs @@ -1,3 +1,5 @@ +using AIStudio.Tools.PluginSystem.Assistants.Icons; + namespace AIStudio.Tools.PluginSystem.Assistants.DataModel; internal sealed class AssistantTextArea : AssistantComponentBase @@ -111,4 +113,6 @@ internal sealed class AssistantTextArea : AssistantComponentBase public Adornment GetAdornmentPos() => Enum.TryParse(this.Adornment, out var position) ? position : MudBlazor.Adornment.Start; public Color GetAdornmentColor() => Enum.TryParse(this.AdornmentColor, out var color) ? color : Color.Default; + + public string GetIconSvg() => MudBlazorIconRegistry.TryGetSvg(this.AdornmentIcon, out var svg) ? svg : string.Empty; } diff --git a/app/SourceGeneratedMappings/MappingRegistryGenerator.cs b/app/SourceGeneratedMappings/MappingRegistryGenerator.cs new file mode 100644 index 00000000..668f0d72 --- /dev/null +++ b/app/SourceGeneratedMappings/MappingRegistryGenerator.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace SourceGeneratedMappings; + +[Generator] +public sealed class MappingRegistryGenerator : IIncrementalGenerator +{ + private const string GENERATED_NAMESPACE = "AIStudio.Tools.PluginSystem.Assistants.Icons"; + private const string ROOT_TYPE_NAME = "MudBlazor.Icons"; + private static readonly string[] ALLOWED_GROUP_PATHS = ["Material.Filled", "Material.Outlined"]; + + private static readonly DiagnosticDescriptor ROOT_TYPE_MISSING = new( + id: "MBI001", + title: "MudBlazor icon root type was not found", + messageFormat: "The generator could not find '{0}' in the current compilation references. No icon registry was generated.", + category: "SourceGeneration", + DiagnosticSeverity.Info, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor NO_ICONS_FOUND = new( + id: "MBI002", + title: "No MudBlazor icons were discovered", + messageFormat: "The generator found '{0}', but no nested icon constants were discovered below it.", + category: "SourceGeneration", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterSourceOutput(context.CompilationProvider, static (spc, compilation) => + { + Generate(spc, compilation); + }); + } + + private static void Generate(SourceProductionContext context, Compilation compilation) + { + var rootType = compilation.GetTypeByMetadataName(ROOT_TYPE_NAME); + if (rootType is null) + { + context.ReportDiagnostic(Diagnostic.Create(ROOT_TYPE_MISSING, Location.None, ROOT_TYPE_NAME)); + return; + } + + var icons = new List(); + CollectIcons(rootType, new List(), icons); + + if (icons.Count == 0) + { + context.ReportDiagnostic(Diagnostic.Create(NO_ICONS_FOUND, Location.None, ROOT_TYPE_NAME)); + return; + } + + var source = RenderSource(icons); + context.AddSource("MudBlazorIconRegistry.g.cs", SourceText.From(source, Encoding.UTF8)); + } + + private static void CollectIcons(INamedTypeSymbol currentType, List path, List icons) + { + foreach (var nestedType in currentType.GetTypeMembers().OrderBy(static t => t.Name, StringComparer.Ordinal)) + { + path.Add(nestedType.Name); + CollectIcons(nestedType, path, icons); + path.RemoveAt(path.Count - 1); + } + + foreach (var field in currentType.GetMembers().OfType().OrderBy(static f => f.Name, StringComparer.Ordinal)) + { + if (!field.IsConst || field.Type.SpecialType != SpecialType.System_String || field.ConstantValue is not string svg) + continue; + + if (path.Count == 0) + continue; + + var groupPath = string.Join(".", path); + if (!ALLOWED_GROUP_PATHS.Contains(groupPath, StringComparer.Ordinal)) + continue; + + icons.Add(new IconDefinition( + QualifiedName: $"Icons.{groupPath}.{field.Name}", + Svg: svg)); + } + } + + private static string RenderSource(IReadOnlyList icons) + { + var builder = new StringBuilder(); + + builder.AppendLine("// "); + builder.AppendLine("#nullable enable"); + builder.AppendLine("using System;"); + builder.AppendLine("using System.Collections.Generic;"); + builder.AppendLine(); + builder.Append("namespace ").Append(GENERATED_NAMESPACE).AppendLine(";"); + builder.AppendLine(); + builder.AppendLine("public static class MudBlazorIconRegistry"); + builder.AppendLine("{"); + builder.AppendLine(" public static readonly IReadOnlyDictionary SvgByIdentifier = new Dictionary(StringComparer.Ordinal)"); + builder.AppendLine(" {"); + + foreach (var icon in icons) + { + builder.Append(" [") + .Append(ToLiteral(icon.QualifiedName)) + .Append("] = ") + .Append(ToLiteral(icon.Svg)) + .AppendLine(","); + } + + builder.AppendLine(" };"); + builder.AppendLine(); + builder.AppendLine(" public static bool TryGetSvg(string identifier, out string svg)"); + builder.AppendLine(" {"); + builder.AppendLine(" return SvgByIdentifier.TryGetValue(identifier, out svg!);"); + builder.AppendLine(" }"); + builder.AppendLine("}"); + + return builder.ToString(); + } + + private static string ToLiteral(string value) + { + return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(value, quote: true); + } + + private sealed record IconDefinition(string QualifiedName, string Svg); +} diff --git a/app/SourceGeneratedMappings/SourceGeneratedMappings.csproj b/app/SourceGeneratedMappings/SourceGeneratedMappings.csproj new file mode 100644 index 00000000..aa671143 --- /dev/null +++ b/app/SourceGeneratedMappings/SourceGeneratedMappings.csproj @@ -0,0 +1,29 @@ + + + + net9.0 + false + enable + latest + + true + true + + SourceGeneratedMappings + SourceGeneratedMappings + 1.0.0 + SourceGeneratedMappings + + + + + $(MSBuildSDKsPath)\..\Roslyn\bincore\Microsoft.CodeAnalysis.dll + false + + + $(MSBuildSDKsPath)\..\Roslyn\bincore\Microsoft.CodeAnalysis.CSharp.dll + false + + + +