sasalka/Code/UI/Chat.razor
2025-06-26 01:56:08 +03:00

157 lines
3.5 KiB
Plaintext

@using Sandbox
@using System
@using Sandbox.UI
@namespace Sandbox
@inherits PanelComponent
@implements Component.INetworkListener
<root>
<div class="output">
@foreach ( var entry in Entries )
{
<ChatEntry Type="@entry.Type"
SteamID="@entry.SteamID"
Author="@entry.Author"
Message="@entry.Message"
IsTemporary="@entry.IsTemporary"/>
}
</div>
<div class="input-container">
<TextEntry @ref=" InputBox" onsubmit="@ChatFinished"/>
</div>
</root>
@code {
public static Chat Instance;
public Chat() => Instance = this;
public static bool IsActive = false;
public enum MessageType
{
Player,
System,
Admin,
Notification
}
public record Entry
{
public Entry( Chat.MessageType type, ulong steamID, string author, string message, RealTimeSince timeSinceAdded, bool isTemporary )
{
Type = type;
SteamID = steamID;
Author = author;
Message = message;
TimeSinceAdded = timeSinceAdded;
IsTemporary = isTemporary;
}
public Chat.MessageType Type { get; internal set; }
public ulong SteamID { get; internal set; }
public string Author { get; internal set; }
public string Message { get; internal set; }
public RealTimeSince TimeSinceAdded { get; internal set; }
public bool IsTemporary { get; internal set; }
}
List<Entry> Entries = new();
public TextEntry InputBox;
public event Action<string> OnChat;
protected override void OnUpdate()
{
if ( InputBox is null ) return;
Panel.AcceptsFocus = false;
if ( Input.Pressed( "chat" ) )
Open();
if ( InputBox.HasFocus && Input.EscapePressed )
{
Input.EscapePressed = false;
ChatClosed();
}
SetClass( "open", InputBox.HasFocus );
}
public static void Open()
{
IsActive = true;
Instance.InputBox?.Focus();
foreach ( var entry in Instance.Entries )
entry.IsTemporary = false;
Instance.StateHasChanged();
}
public static void AddMessage( MessageType type, string message, ulong steamId = 0, string author = "" ) => Instance?.AddMessageInternal( type, message, steamId, author );
[ConCmd( "say" )]
public static void Say( string message )
{
Instance?.AddTextInternal( message );
}
[Rpc.Broadcast]
public void AddTextInternal( string message )
{
if ( string.IsNullOrWhiteSpace( message ) ) return;
AddMessageInternal( MessageType.Player, message.Truncate( 300 ), Rpc.Caller.SteamId, Rpc.Caller.DisplayName );
}
void AddMessageInternal( MessageType type, string message, ulong steamId = 0, string author = "" )
{
Entries.Add( new Entry( type, steamId, author, message, 0, !IsActive ) );
StateHasChanged();
Log.Info( $"[{type}] {author}: {message}" );
}
void ScrollToBottom()
{
var panel = Panel.Children.First();
panel.ScrollVelocity = 0;
panel.ScrollOffset = 0;
}
void ChatFinished()
{
IsActive = false;
var text = InputBox.Text;
InputBox.Text = "";
if ( !string.IsNullOrWhiteSpace( text ) )
{
OnChat?.Invoke( text );
AddTextInternal( text );
}
ScrollToBottom();
OnChat = null;
}
void ChatClosed()
{
IsActive = false;
InputBox.Text = "";
ScrollToBottom();
OnChat = null;
}
void Component.INetworkListener.OnConnected( Connection channel )
{
if ( IsProxy ) return;
AddMessageInternal( MessageType.System, $"{channel.DisplayName} has joined the game" );
}
void Component.INetworkListener.OnDisconnected( Connection channel )
{
if ( IsProxy ) return;
AddMessageInternal( MessageType.System, $"{channel.DisplayName} has left the game" );
}
}