This commit is contained in:
Benjamin Palko 2025-05-22 09:03:10 -04:00
parent edb2cb6ef9
commit 0017fc4c9f
8 changed files with 200 additions and 143 deletions

5
client/.editorconfig Normal file
View file

@ -0,0 +1,5 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true

39
client/chat/ChatInput.cs Normal file
View file

@ -0,0 +1,39 @@
using Godot;
using SpacetimeDB;
using SpacetimeDB.Types;
public partial class ChatInput : LineEdit
{
public override void _EnterTree()
{
TextSubmitted += OnMessageInput;
}
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
DbConnection conn = Spacetime.Instance.Connection;
RegisterSubscriptions(conn);
}
void OnMessageInput(string text)
{
Spacetime.Instance.Connection.Reducers.SendMessage(text);
Text = "";
}
void RegisterSubscriptions(DbConnection conn)
{
conn.Reducers.OnSendMessage += Reducer_OnSendMessageEvent;
}
/// Our `OnSendMessageEvent` callback: print a warning if the reducer failed.
void Reducer_OnSendMessageEvent(ReducerEventContext ctx, string text)
{
var e = ctx.Event;
if (e.CallerIdentity == Spacetime.Instance.Identity && e.Status is Status.Failed(var error))
{
GD.PrintErr($"Failed to send message {text}: {error}");
}
}
}

View file

@ -0,0 +1 @@
uid://115ssbk8epio

View file

@ -1,20 +1,87 @@
using Godot; using Godot;
using SpacetimeDB;
using SpacetimeDB.Types;
public partial class ChatLog : RichTextLabel public partial class ChatLog : RichTextLabel
{ {
// Called when the node enters the scene tree for the first time. const string SystemColor = "#747474";
public override void _Ready()
{
}
// Called every frame. 'delta' is the elapsed time since the previous frame. public override void _EnterTree()
public override void _Process(double delta) {
{ ClearLog();
} }
public void PushMessage(string name, string message, string color) public override void _Ready()
{ {
string entry = $"[color={color}]{name}:[/color] {message}"; DbConnection conn = Spacetime.Instance.Connection;
this.Text += $"{entry}\n"; RegisterSubscriptions(conn);
} }
string UserNameOrIdentity(User user)
{
return user != null ? user.Name ?? user.Identity.ToString()[..8] : "unknown";
}
void PushMessage(string name, string message, string color)
{
string entry = $"[color={color}]{name}:[/color] {message}";
this.Text += $"{entry}\n";
}
void ClearLog()
{
Text = "";
}
void RegisterSubscriptions(DbConnection conn)
{
conn.Db.User.OnInsert += User_OnInsert;
conn.Db.User.OnUpdate += User_OnUpdate;
conn.Db.Message.OnInsert += Message_OnInsert;
}
void User_OnInsert(EventContext ctx, User insertedValue)
{
if (ctx.Event is Event<Reducer>.SubscribeApplied)
{
return;
}
PushMessage("System", $"{UserNameOrIdentity(insertedValue)} connected", SystemColor);
}
void User_OnUpdate(EventContext ctx, User oldValue, User newValue)
{
if (oldValue.Name != newValue.Name)
{
PushMessage(
"System",
$"{UserNameOrIdentity(oldValue)} renamed to {UserNameOrIdentity(newValue)}",
SystemColor
);
}
if (oldValue.Online != newValue.Online)
{
if (newValue.Online)
{
PushMessage("System", $"{UserNameOrIdentity(newValue)} connected", SystemColor);
}
else
{
PushMessage("System", $"{UserNameOrIdentity(newValue)} disconnected", SystemColor);
}
}
}
void Message_OnInsert(EventContext ctx, Message insertedValue)
{
User sender = ctx.Db.User.Identity.Find(insertedValue.Sender);
string color = sender.Identity.Equals(Spacetime.Instance.Identity) ? "blue" : "red";
if (ctx.Event is Event<Reducer>.SubscribeApplied)
{
PushMessage(UserNameOrIdentity(sender), insertedValue.Text, color);
return;
}
PushMessage(UserNameOrIdentity(sender), insertedValue.Text, color);
}
} }

View file

@ -0,0 +1,60 @@
using Godot;
using SpacetimeDB;
using SpacetimeDB.Types;
public partial class ChatOptions : HBoxContainer
{
public LineEdit Username { get; private set; }
public ColorPickerButton ColorPicker { get; private set; }
public override void _EnterTree()
{
Username = GetNode<LineEdit>("Username");
ColorPicker = GetNode<ColorPickerButton>("ColorPicker");
Username.TextSubmitted += OnUsernameInput;
Username.FocusExited += ResetUsername;
}
public override void _Ready()
{
DbConnection conn = Spacetime.Instance.Connection;
RegisterSubscriptions(conn);
}
string UserNameOrIdentity(User user)
{
return user != null ? user.Name ?? user.Identity.ToString()[..8] : "unknown";
}
void ResetUsername()
{
Username.Text = UserNameOrIdentity(Spacetime.Instance.Me);
}
void OnUsernameInput(string text)
{
Spacetime.Instance.Connection.Reducers.SetName(text);
}
void RegisterSubscriptions(DbConnection conn)
{
conn.Reducers.OnSetName += Reducer_OnSetNameEvent;
}
/// Our `OnSetNameEvent` callback: print a warning if the reducer failed.
void Reducer_OnSetNameEvent(ReducerEventContext ctx, string name)
{
var e = ctx.Event;
if (e.CallerIdentity != Spacetime.Instance.Identity)
{
// Not me
return;
}
if (e.Status is Status.Failed(var error))
{
GD.PrintErr($"Failed to change name to {name}: {error}");
return;
}
}
}

View file

@ -0,0 +1 @@
uid://tff5u6blrc1d

View file

@ -1,136 +1,16 @@
using Godot; using Godot;
using SpacetimeDB;
using SpacetimeDB.Types;
public partial class ChatWindow : VBoxContainer public partial class ChatWindow : VBoxContainer
{ {
const string SystemColor = "#747474"; private ChatOptions _options;
private ChatLog _log;
private LineEdit _input;
private LineEdit _userNameInput; // Called when the node enters the scene tree for the first time.
private ChatLog _log; public override void _EnterTree()
private LineEdit _input;
// Called when the node enters the scene tree for the first time.
public override void _EnterTree()
{
_userNameInput = GetNode<LineEdit>("Options/Username");
_log = GetNode<ChatLog>("ChatLog");
_input = GetNode<LineEdit>("ChatInput");
_log.Text = "";
}
string UserNameOrIdentity(User user)
{
return user != null ? user.Name ?? user.Identity.ToString()[..8] : "unknown";
}
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
DbConnection conn = Spacetime.Instance.Connection;
_userNameInput.TextSubmitted += OnUsernameInput;
_userNameInput.FocusExited += ResetUsername;
_input.TextSubmitted += OnMessageInput;
conn.Db.User.OnInsert += User_OnInsert;
conn.Db.User.OnUpdate += User_OnUpdate;
conn.Db.Message.OnInsert += Message_OnInsert;
conn.Reducers.OnSetName += Reducer_OnSetNameEvent;
conn.Reducers.OnSendMessage += Reducer_OnSendMessageEvent;
}
void ResetUsername()
{
_userNameInput.Text = UserNameOrIdentity(Spacetime.Instance.Me);
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
private void OnUsernameInput(string text)
{
Spacetime.Instance.Connection.Reducers.SetName(text);
}
private void OnMessageInput(string text)
{
Spacetime.Instance.Connection.Reducers.SendMessage(text);
_input.Text = "";
}
void User_OnInsert(EventContext ctx, User insertedValue)
{
if (ctx.Event is Event<Reducer>.SubscribeApplied)
{ {
if (insertedValue.Identity == Spacetime.Instance.Identity) _options = GetNode<ChatOptions>("Options");
{ _log = GetNode<ChatLog>("ChatLog");
ResetUsername(); _input = GetNode<ChatInput>("ChatInput");
}
return;
} }
_log.PushMessage("System", $"{UserNameOrIdentity(insertedValue)} connected", SystemColor);
}
void User_OnUpdate(EventContext ctx, User oldValue, User newValue)
{
if (oldValue.Name != newValue.Name)
{
_log.PushMessage("System", $"{UserNameOrIdentity(oldValue)} renamed to {UserNameOrIdentity(newValue)}", SystemColor);
}
if (oldValue.Online != newValue.Online)
{
if (newValue.Online)
{
_log.PushMessage("System", $"{UserNameOrIdentity(newValue)} connected", SystemColor);
}
else
{
_log.PushMessage("System", $"{UserNameOrIdentity(newValue)} disconnected", SystemColor);
}
}
}
void Message_OnInsert(EventContext ctx, Message insertedValue)
{
User sender = ctx.Db.User.Identity.Find(insertedValue.Sender);
string color = sender.Identity.Equals(Spacetime.Instance.Identity) ? "blue" : "red";
if (ctx.Event is Event<Reducer>.SubscribeApplied)
{
_log.PushMessage(UserNameOrIdentity(sender), insertedValue.Text, color);
return;
}
_log.PushMessage(UserNameOrIdentity(sender), insertedValue.Text, color);
}
/// Our `OnSetNameEvent` callback: print a warning if the reducer failed.
void Reducer_OnSetNameEvent(ReducerEventContext ctx, string name)
{
var e = ctx.Event;
if (e.CallerIdentity != Spacetime.Instance.Identity)
{
// Not me
return;
}
if (e.Status is Status.Failed(var error))
{
GD.PrintErr($"Failed to change name to {name}: {error}");
return;
}
}
/// Our `OnSendMessageEvent` callback: print a warning if the reducer failed.
void Reducer_OnSendMessageEvent(ReducerEventContext ctx, string text)
{
var e = ctx.Event;
if (e.CallerIdentity == Spacetime.Instance.Identity && e.Status is Status.Failed(var error))
{
GD.PrintErr($"Failed to send message {text}: {error}");
}
}
} }

View file

@ -1,7 +1,9 @@
[gd_scene load_steps=3 format=3 uid="uid://cqmy41vtnqd6f"] [gd_scene load_steps=5 format=3 uid="uid://cqmy41vtnqd6f"]
[ext_resource type="Script" uid="uid://c3s41dv7hv4md" path="res://chat/ChatWindow.cs" id="1_d8jvm"] [ext_resource type="Script" uid="uid://c3s41dv7hv4md" path="res://chat/ChatWindow.cs" id="1_d8jvm"]
[ext_resource type="Script" uid="uid://cgn6wa7td0ekp" path="res://chat/ChatLog.cs" id="2_fkxbv"] [ext_resource type="Script" uid="uid://cgn6wa7td0ekp" path="res://chat/ChatLog.cs" id="2_fkxbv"]
[ext_resource type="Script" uid="uid://tff5u6blrc1d" path="res://chat/ChatOptions.cs" id="2_lvlsn"]
[ext_resource type="Script" uid="uid://115ssbk8epio" path="res://chat/ChatInput.cs" id="4_yd183"]
[node name="Chat Window" type="VBoxContainer"] [node name="Chat Window" type="VBoxContainer"]
anchors_preset = 15 anchors_preset = 15
@ -14,6 +16,7 @@ script = ExtResource("1_d8jvm")
[node name="Options" type="HBoxContainer" parent="."] [node name="Options" type="HBoxContainer" parent="."]
layout_mode = 2 layout_mode = 2
alignment = 2 alignment = 2
script = ExtResource("2_lvlsn")
[node name="Label" type="Label" parent="Options"] [node name="Label" type="Label" parent="Options"]
layout_mode = 2 layout_mode = 2
@ -23,7 +26,7 @@ text = "Username:"
custom_minimum_size = Vector2(120, 0) custom_minimum_size = Vector2(120, 0)
layout_mode = 2 layout_mode = 2
[node name="ColorPickerButton" type="ColorPickerButton" parent="Options"] [node name="ColorPicker" type="ColorPickerButton" parent="Options"]
layout_mode = 2 layout_mode = 2
text = "Color" text = "Color"
@ -38,3 +41,4 @@ script = ExtResource("2_fkxbv")
[node name="ChatInput" type="LineEdit" parent="."] [node name="ChatInput" type="LineEdit" parent="."]
layout_mode = 2 layout_mode = 2
script = ExtResource("4_yd183")