UI/SpawnMenu/UtilityTab.razor
@using Sandbox;
@using Sandbox.UI;
@inherits Panel
@namespace Sandbox
@implements IUtilityTab
@attribute [Icon("🌍")]
@attribute [Title("#spawnmenu.tab.utilities")]
@attribute [Order(0)]

<root class="tab">
    <div class="left">
        <VerticalMenu class="menuinner">
            <Options>
                @foreach ( var group in GetPageGroups() )
                {
                    @if ( !string.IsNullOrWhiteSpace( group.Key ) )
                    {
                        <h2>@group.Key</h2>
                    }

                    @foreach ( var type in group.OrderBy( x => x.Order )
                        .ThenBy( x => SortLabel( x.Title ), StringComparer.CurrentCultureIgnoreCase )
                        .ThenBy( x => x.FullName, StringComparer.Ordinal ) )
                    {
                        <MenuOption Text="@type.Title" Icon="@type.Icon"
                            class=@( SelectedPageType == type ? "active" : "" )
                            @onclick="@(() => OnSelect( type ))">
                        </MenuOption>
                    }
                }
            </Options>
        </VerticalMenu>
    </div>

    <div class="body menuinner" @ref="PageContainer"></div>
</root>

@code
{
    TypeDescription SelectedPageType { get; set; }
    Panel PageContainer { get; set; }
    UtilityPage ActivePage;

    IEnumerable<IGrouping<string, TypeDescription>> GetPageGroups()
    {
        return GetVisiblePages()
            .GroupBy( x => x.Group )
            .OrderBy( group => group.Min( type => type.Order ) )
            .ThenBy( group => SortLabel( group.Key ), StringComparer.CurrentCultureIgnoreCase )
            .ThenBy( group => group.Key, StringComparer.Ordinal );
    }

    static string SortLabel( string label )
    {
        if ( string.IsNullOrEmpty( label ) ) return "";
        return label.StartsWith( '#' ) ? Game.Language.GetPhrase( label.TrimStart( '#' ) ) : label;
    }

    IEnumerable<TypeDescription> GetVisiblePages()
    {
        foreach ( var type in Game.TypeLibrary.GetTypes<UtilityPage>() )
        {
            if ( type.IsAbstract ) continue;
            var instance = type.Create<UtilityPage>();
            if ( instance is null || !instance.IsPageVisible() ) continue;
            instance.Delete();
            yield return type;
        }
    }

    void OnSelect( TypeDescription type )
    {
        SelectedPageType = type;

        ActivePage?.Delete();
        ActivePage = type.Create<UtilityPage>();
        PageContainer.AddChild( ActivePage );

        StateHasChanged();
    }
}