UI/Chat/Chatbox.razor
@using Sandbox.UI
@using Sandbox.Services
@using Sandbox.Audio
@using System
@attribute [Icon("message", "red", "white")]
@inherits PanelComponent
@namespace Donut.UI

<root>
    <div class="chat_canvas">
        @foreach (var entry in Entries)
        {
            <ChatEntry Entry=@entry />
        }
    </div>
    <div class="input_canvas">
        <TextEntry @ref="InputBox" placeholder="#chat.placeholder" onsubmit=@ChatFinished AllowEmojiReplace=@(true) />
    </div>
</root>

@code
{
    public static Chatbox Instance { get; private set; }

    public record Entry(ulong SteamId, string Name, string Message, string Styles, RealTimeSince TimeSinceBorn);

    private TextEntry InputBox;
    private List<Entry> Entries = new();

    protected override void OnAwake()
    {
        Instance = this;
    }

    protected override void OnUpdate()
    {
        if (InputBox is null) return;

        Panel.AcceptsFocus = false;

        if (Input.Pressed("Chat"))
        {
            InputBox.Focus();
            Sound.Play("select").TargetMixer = Mixer.FindMixerByName("UI");
        }

        // todo: figure out a better way to prune entries 
        //       and not tank performance, perhaps as a setting?
        if (Entries.Count > 25)
        {
            Entries.RemoveAt(0);
            StateHasChanged();
        }

        SetClass("open", InputBox.HasFocus);
    }

    void ChatFinished()
    {
        string text = InputBox.Text.Trim().Substring(0, Math.Min(InputBox.Text.Trim().Length, 1024));
        string styles = "";

        InputBox.Text = "";

        if (string.IsNullOrWhiteSpace(text)) return;

        if (ResourceLibrary.Get<GamePass>("data/donator.gamepass").Has())
            styles += " rainbow";

        AddMessage(Game.SteamId.ToString(), text, styles);
    }

    [Broadcast]
    public void AddMessage(string sender, string message, string styles = "")
    {
        if (string.IsNullOrWhiteSpace(message)) return;

        ulong steamId = 0;
        string name = "";

        if (ulong.TryParse(sender, out steamId))
        {
            var friend = new Friend((long)steamId);
            name = friend.Name;
            steamId = friend.Id;
        }
        else
        {
            name = sender;
        }

        Entries.Add(new Entry(steamId, name, message, styles, 0));
        Sound.Play("talk").TargetMixer = Mixer.FindMixerByName("UI");
        StateHasChanged();
    }

    public void AddLocalMessage(string sender, string message, string styles = "")
    {
        if (string.IsNullOrWhiteSpace(message)) return;

        Entries.Add(new Entry(0, sender, message, styles, 0));
        Sound.Play("talk").TargetMixer = Mixer.FindMixerByName("UI");
        StateHasChanged();
    }
}