feat: 2.3.1
This commit is contained in:
+365
-2
@@ -38,6 +38,8 @@ var _current_dr_month: String = ""
|
||||
|
||||
# Tab: Announcements
|
||||
@onready var target_user_edit := %TargetUserEdit as LineEdit
|
||||
@onready var find_user_btn := %FindUserBtn as Button
|
||||
@onready var resolved_id_label := %ResolvedIdLabel as Label
|
||||
@onready var title_edit := %TitleEdit as LineEdit
|
||||
@onready var content_edit := %ContentEdit as TextEdit
|
||||
@onready var start_date_edit := %StartDatePicker as Button
|
||||
@@ -47,6 +49,18 @@ var _current_dr_month: String = ""
|
||||
@onready var reward_row_template := %RewardRowTemplate as HBoxContainer
|
||||
@onready var send_mail_btn := %SendMailBtn as Button
|
||||
|
||||
var _resolved_user_id: String = ""
|
||||
|
||||
# Tab: Mail Manager
|
||||
@onready var mail_tree := %MailTree as Tree
|
||||
@onready var refresh_mail_btn := %RefreshMailBtn as Button
|
||||
@onready var edit_mail_btn := %EditMailBtn as Button
|
||||
@onready var end_mail_btn := %EndMailBtn as Button
|
||||
@onready var delete_mail_server_btn := %DeleteMailServerBtn as Button
|
||||
|
||||
var _mail_root: TreeItem
|
||||
var _all_server_mails: Array = []
|
||||
|
||||
const MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
||||
|
||||
# -- Data --
|
||||
@@ -140,6 +154,19 @@ func _setup_columns() -> void:
|
||||
lb_tree.set_column_custom_minimum_width(5, 60)
|
||||
_lb_root = lb_tree.create_item()
|
||||
|
||||
# Mail Manager
|
||||
mail_tree.set_column_title(0, "Type")
|
||||
mail_tree.set_column_title(1, "Title")
|
||||
mail_tree.set_column_title(2, "Sender")
|
||||
mail_tree.set_column_title(3, "Start")
|
||||
mail_tree.set_column_title(4, "Expires")
|
||||
mail_tree.set_column_title(5, "Status")
|
||||
mail_tree.set_column_custom_minimum_width(0, 100)
|
||||
mail_tree.set_column_expand(0, false)
|
||||
mail_tree.set_column_custom_minimum_width(5, 80)
|
||||
mail_tree.set_column_expand(5, false)
|
||||
_mail_root = mail_tree.create_item()
|
||||
|
||||
func _connect_signals() -> void:
|
||||
close_btn.pressed.connect(_on_close)
|
||||
refresh_btn.pressed.connect(_on_refresh)
|
||||
@@ -166,6 +193,16 @@ func _connect_signals() -> void:
|
||||
# Announcement actions
|
||||
send_mail_btn.pressed.connect(_on_send_mail)
|
||||
add_reward_btn.pressed.connect(_on_add_reward_pressed)
|
||||
find_user_btn.pressed.connect(_on_find_user)
|
||||
target_user_edit.text_changed.connect(func(_t): _resolved_user_id = ""; resolved_id_label.text = "")
|
||||
|
||||
# Mail Manager actions
|
||||
refresh_mail_btn.pressed.connect(func(): await _load_mail())
|
||||
mail_tree.item_selected.connect(_on_mail_item_selected)
|
||||
edit_mail_btn.pressed.connect(_on_edit_mail_pressed)
|
||||
end_mail_btn.pressed.connect(_on_end_mail_pressed)
|
||||
delete_mail_server_btn.pressed.connect(_on_delete_mail_server_pressed)
|
||||
_update_mail_action_btns(null)
|
||||
|
||||
# =============================================================================
|
||||
# Core Panel Logic
|
||||
@@ -189,6 +226,8 @@ func _on_tab_changed(tab_index: int) -> void:
|
||||
await _load_leaderboard()
|
||||
elif tab_index == 2:
|
||||
await _load_daily_rewards_config()
|
||||
elif tab_index == 4:
|
||||
await _load_mail()
|
||||
|
||||
# =============================================================================
|
||||
# RPC Helper
|
||||
@@ -649,8 +688,60 @@ func _on_add_reward_pressed() -> void:
|
||||
var remove_btn = row.get_node("RemoveBtn") as Button
|
||||
remove_btn.pressed.connect(func(): row.queue_free())
|
||||
|
||||
func _on_find_user() -> void:
|
||||
var input = target_user_edit.text.strip_edges()
|
||||
if input.is_empty():
|
||||
_resolved_user_id = ""
|
||||
resolved_id_label.text = "(Global — all users)"
|
||||
return
|
||||
|
||||
_set_status("Looking up user...")
|
||||
var uid = await _resolve_target_user_id(input)
|
||||
if uid.is_empty():
|
||||
resolved_id_label.text = "NOT FOUND"
|
||||
resolved_id_label.add_theme_color_override("font_color", CLR_STATUS_ERR)
|
||||
_set_status("User not found: " + input, CLR_STATUS_ERR)
|
||||
else:
|
||||
_resolved_user_id = uid
|
||||
resolved_id_label.text = "ID: " + uid.substr(0, 12) + "..."
|
||||
resolved_id_label.add_theme_color_override("font_color", CLR_STATUS_OK)
|
||||
_set_status("Found user: " + uid, CLR_STATUS_OK)
|
||||
|
||||
func _resolve_target_user_id(input: String) -> String:
|
||||
"""Resolve a username, display_name, or user_id to a user_id.
|
||||
Returns empty string if not found."""
|
||||
if input.is_empty():
|
||||
return ""
|
||||
|
||||
# If it looks like a UUID already, return as-is
|
||||
if input.length() >= 32 and "-" in input:
|
||||
return input
|
||||
|
||||
# Search in cached all_users first
|
||||
for u in all_users:
|
||||
var uname: String = u.get("username", "")
|
||||
var dname: String = u.get("display_name", "")
|
||||
var uid: String = u.get("user_id", "")
|
||||
if uname.to_lower() == input.to_lower() or dname.to_lower() == input.to_lower():
|
||||
return uid
|
||||
|
||||
# Cache miss — fetch from server
|
||||
var res := await _rpc("admin_list_users", {})
|
||||
if res.has("error"):
|
||||
return ""
|
||||
all_users = res.get("users", [])
|
||||
|
||||
for u in all_users:
|
||||
var uname: String = u.get("username", "")
|
||||
var dname: String = u.get("display_name", "")
|
||||
var uid: String = u.get("user_id", "")
|
||||
if uname.to_lower() == input.to_lower() or dname.to_lower() == input.to_lower():
|
||||
return uid
|
||||
|
||||
return ""
|
||||
|
||||
func _on_send_mail() -> void:
|
||||
var target = target_user_edit.text.strip_edges()
|
||||
var input = target_user_edit.text.strip_edges()
|
||||
var title = title_edit.text.strip_edges()
|
||||
var content = content_edit.text.strip_edges()
|
||||
var start_date = start_date_edit.get_date_iso()
|
||||
@@ -659,6 +750,21 @@ func _on_send_mail() -> void:
|
||||
if title.is_empty() or content.is_empty():
|
||||
_set_status("Title and content cannot be empty", CLR_STATUS_ERR)
|
||||
return
|
||||
|
||||
# Resolve target
|
||||
var target_uid := ""
|
||||
if not input.is_empty():
|
||||
if not _resolved_user_id.is_empty():
|
||||
target_uid = _resolved_user_id
|
||||
else:
|
||||
_set_status("Resolving user...")
|
||||
target_uid = await _resolve_target_user_id(input)
|
||||
if target_uid.is_empty():
|
||||
_set_status("User not found: " + input + ". Use Find button.", CLR_STATUS_ERR)
|
||||
return
|
||||
_resolved_user_id = target_uid
|
||||
resolved_id_label.text = "ID: " + target_uid.substr(0, 12) + "..."
|
||||
resolved_id_label.add_theme_color_override("font_color", CLR_STATUS_OK)
|
||||
|
||||
var rewards_arr = []
|
||||
for child in rewards_list.get_children():
|
||||
@@ -679,7 +785,7 @@ func _on_send_mail() -> void:
|
||||
_set_status("Sending mail...")
|
||||
|
||||
var payload = {
|
||||
"target_user_id": target,
|
||||
"target_user_id": target_uid,
|
||||
"title": title,
|
||||
"content": content,
|
||||
"start_date": start_date,
|
||||
@@ -694,6 +800,9 @@ func _on_send_mail() -> void:
|
||||
_set_status("Failed to send mail: " + res.get("error", "Unknown"), CLR_STATUS_ERR)
|
||||
else:
|
||||
_set_status("Mail sent successfully!", CLR_STATUS_OK)
|
||||
target_user_edit.text = ""
|
||||
_resolved_user_id = ""
|
||||
resolved_id_label.text = ""
|
||||
title_edit.text = ""
|
||||
content_edit.text = ""
|
||||
if start_date_edit.has_method("clear_date"):
|
||||
@@ -702,3 +811,257 @@ func _on_send_mail() -> void:
|
||||
end_date_edit.clear_date()
|
||||
for child in rewards_list.get_children():
|
||||
if child.visible: child.queue_free()
|
||||
|
||||
# =============================================================================
|
||||
# TAB 5: MAIL MANAGER
|
||||
# =============================================================================
|
||||
func _load_mail() -> void:
|
||||
_clear_tree(mail_tree, _mail_root)
|
||||
_set_status("Loading mails...")
|
||||
|
||||
var res := await _rpc("admin_list_mail", {})
|
||||
if res.has("error"):
|
||||
_set_status("Failed: " + str(res.error), CLR_STATUS_ERR)
|
||||
return
|
||||
|
||||
_all_server_mails = res.get("mails", [])
|
||||
count_label.text = "%d mails" % _all_server_mails.size()
|
||||
|
||||
var now_str = Time.get_datetime_string_from_system(true)
|
||||
|
||||
for mail in _all_server_mails:
|
||||
var item := _mail_root.create_child()
|
||||
var mail_type: String = mail.get("type", "global")
|
||||
var mail_title: String = mail.get("title", "No Title")
|
||||
var sender: String = mail.get("sender", "SYSTEM")
|
||||
var start_date: String = mail.get("start_date", "")
|
||||
var expiry: String = mail.get("expiry_date", "")
|
||||
|
||||
# Type column
|
||||
item.set_text(0, mail_type.to_upper())
|
||||
if mail_type == "personal":
|
||||
item.set_custom_color(0, CLR_ADMIN)
|
||||
else:
|
||||
item.set_custom_color(0, CLR_MOD)
|
||||
|
||||
# Title
|
||||
item.set_text(1, mail_title)
|
||||
|
||||
# Sender
|
||||
item.set_text(2, sender)
|
||||
|
||||
# Start date
|
||||
item.set_text(3, start_date.substr(0, 10) if start_date.length() >= 10 else start_date)
|
||||
|
||||
# Expiry
|
||||
var expiry_short = expiry.substr(0, 10) if expiry.length() >= 10 else expiry
|
||||
item.set_text(4, expiry_short)
|
||||
|
||||
# Status
|
||||
var end_date: String = mail.get("end_date", "")
|
||||
var status := "ACTIVE"
|
||||
if not end_date.is_empty() and now_str > end_date:
|
||||
status = "ENDED"
|
||||
item.set_custom_color(5, CLR_BTN_DEL)
|
||||
elif not expiry.is_empty() and now_str > expiry:
|
||||
status = "EXPIRED"
|
||||
item.set_custom_color(5, CLR_BTN_DEL)
|
||||
else:
|
||||
item.set_custom_color(5, CLR_STATUS_OK)
|
||||
item.set_text(5, status)
|
||||
|
||||
item.set_metadata(0, mail)
|
||||
|
||||
_update_mail_action_btns(null)
|
||||
_set_status("")
|
||||
|
||||
func _on_mail_item_selected() -> void:
|
||||
var item = mail_tree.get_selected()
|
||||
_update_mail_action_btns(item)
|
||||
|
||||
func _update_mail_action_btns(item) -> void:
|
||||
var has_sel = item != null
|
||||
edit_mail_btn.disabled = not has_sel
|
||||
end_mail_btn.disabled = not has_sel
|
||||
delete_mail_server_btn.disabled = not has_sel
|
||||
|
||||
func _get_selected_mail() -> Dictionary:
|
||||
var item = mail_tree.get_selected()
|
||||
if item:
|
||||
return item.get_metadata(0)
|
||||
return {}
|
||||
|
||||
func _on_edit_mail_pressed() -> void:
|
||||
var mail = _get_selected_mail()
|
||||
if mail.is_empty(): return
|
||||
_show_edit_mail_dialog(mail)
|
||||
|
||||
func _show_edit_mail_dialog(mail: Dictionary) -> void:
|
||||
var mail_id: String = mail.get("id", "")
|
||||
var mail_type: String = mail.get("type", "global")
|
||||
var target_uid: String = mail.get("target_user_id", "")
|
||||
|
||||
var dialog := AcceptDialog.new()
|
||||
dialog.title = "Edit Mail: " + mail.get("title", "")
|
||||
dialog.min_size = Vector2i(480, 360)
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.add_theme_constant_override("separation", 10)
|
||||
|
||||
var id_lbl := Label.new()
|
||||
id_lbl.text = "ID: " + mail_id + " | Type: " + mail_type
|
||||
id_lbl.add_theme_color_override("font_color", CLR_DIM)
|
||||
vbox.add_child(id_lbl)
|
||||
|
||||
var grid := GridContainer.new()
|
||||
grid.columns = 2
|
||||
grid.add_theme_constant_override("h_separation", 8)
|
||||
grid.add_theme_constant_override("v_separation", 8)
|
||||
|
||||
var title_lbl := Label.new(); title_lbl.text = "Title:"; grid.add_child(title_lbl)
|
||||
var title_input := LineEdit.new(); title_input.text = mail.get("title", ""); title_input.custom_minimum_size.x = 300; grid.add_child(title_input)
|
||||
|
||||
var content_lbl := Label.new(); content_lbl.text = "Content:"; grid.add_child(content_lbl)
|
||||
var content_input := TextEdit.new(); content_input.text = mail.get("content", ""); content_input.custom_minimum_size = Vector2(300, 100); grid.add_child(content_input)
|
||||
|
||||
var end_lbl := Label.new(); end_lbl.text = "End Date:"; grid.add_child(end_lbl)
|
||||
var end_picker: Button = load("res://scenes/ui/date_picker.tscn").instantiate()
|
||||
end_picker.custom_minimum_size.x = 200
|
||||
grid.add_child(end_picker)
|
||||
# Pre-populate if existing end_date
|
||||
var existing_end: String = mail.get("end_date", "")
|
||||
if not existing_end.is_empty():
|
||||
var parts = existing_end.substr(0, 10).split("-")
|
||||
if parts.size() == 3:
|
||||
end_picker.current_date = {"year": int(parts[0]), "month": int(parts[1]), "day": int(parts[2])}
|
||||
end_picker.view_date = end_picker.current_date.duplicate()
|
||||
end_picker.text = existing_end.substr(0, 10)
|
||||
|
||||
# Recipient row
|
||||
var recip_lbl := Label.new(); recip_lbl.text = "Recipient:"; grid.add_child(recip_lbl)
|
||||
var recip_hbox := HBoxContainer.new()
|
||||
recip_hbox.add_theme_constant_override("separation", 6)
|
||||
var recip_input := LineEdit.new()
|
||||
recip_input.custom_minimum_size.x = 180
|
||||
recip_input.placeholder_text = "Username or ID (empty = global)"
|
||||
if mail_type == "personal" and not target_uid.is_empty():
|
||||
recip_input.text = target_uid.substr(0, 12) + "..."
|
||||
recip_input.tooltip_text = target_uid
|
||||
recip_hbox.add_child(recip_input)
|
||||
var recip_find_btn := Button.new()
|
||||
recip_find_btn.text = "Find"
|
||||
recip_find_btn.custom_minimum_size.x = 60
|
||||
recip_hbox.add_child(recip_find_btn)
|
||||
var recip_resolved_lbl := Label.new()
|
||||
recip_resolved_lbl.add_theme_color_override("font_color", CLR_STATUS_OK)
|
||||
recip_hbox.add_child(recip_resolved_lbl)
|
||||
grid.add_child(recip_hbox)
|
||||
|
||||
var _edit_resolved_uid := target_uid
|
||||
recip_find_btn.pressed.connect(func():
|
||||
var inp = recip_input.text.strip_edges()
|
||||
if inp.is_empty():
|
||||
_edit_resolved_uid = ""
|
||||
recip_resolved_lbl.text = "(Global)"
|
||||
recip_resolved_lbl.add_theme_color_override("font_color", CLR_STATUS_OK)
|
||||
return
|
||||
var uid = await _resolve_target_user_id(inp)
|
||||
if uid.is_empty():
|
||||
recip_resolved_lbl.text = "NOT FOUND"
|
||||
recip_resolved_lbl.add_theme_color_override("font_color", CLR_STATUS_ERR)
|
||||
else:
|
||||
_edit_resolved_uid = uid
|
||||
recip_resolved_lbl.text = uid.substr(0, 12) + "..."
|
||||
recip_resolved_lbl.add_theme_color_override("font_color", CLR_STATUS_OK)
|
||||
)
|
||||
|
||||
vbox.add_child(grid)
|
||||
|
||||
var save_btn := Button.new()
|
||||
save_btn.text = "Save Changes"
|
||||
save_btn.custom_minimum_size.y = 40
|
||||
vbox.add_child(save_btn)
|
||||
|
||||
dialog.add_child(vbox)
|
||||
add_child(dialog)
|
||||
dialog.popup_centered()
|
||||
|
||||
save_btn.pressed.connect(func():
|
||||
# If user typed something but didn't click Find, auto-resolve
|
||||
var recip_text = recip_input.text.strip_edges()
|
||||
var final_target = _edit_resolved_uid
|
||||
if not recip_text.is_empty() and _edit_resolved_uid == target_uid:
|
||||
# Text changed but not resolved yet
|
||||
var resolved = await _resolve_target_user_id(recip_text)
|
||||
if not resolved.is_empty():
|
||||
final_target = resolved
|
||||
elif recip_text.is_empty():
|
||||
final_target = ""
|
||||
|
||||
_set_status("Updating mail...")
|
||||
var payload = {
|
||||
"mail_id": mail_id,
|
||||
"type": mail_type,
|
||||
"target_user_id": target_uid,
|
||||
"new_target_user_id": final_target,
|
||||
"title": title_input.text,
|
||||
"content": content_input.text,
|
||||
"end_date": end_picker.get_date_iso()
|
||||
}
|
||||
var res = await _rpc("admin_update_mail", payload)
|
||||
if res.has("success"):
|
||||
_set_status("Mail updated!", CLR_STATUS_OK)
|
||||
await _load_mail()
|
||||
dialog.queue_free()
|
||||
)
|
||||
|
||||
func _on_end_mail_pressed() -> void:
|
||||
var mail = _get_selected_mail()
|
||||
if mail.is_empty(): return
|
||||
|
||||
var mail_id: String = mail.get("id", "")
|
||||
var mail_type: String = mail.get("type", "global")
|
||||
var target_uid: String = mail.get("target_user_id", "")
|
||||
|
||||
var confirm := ConfirmationDialog.new()
|
||||
confirm.title = "End Mail Now?"
|
||||
confirm.dialog_text = "Set end_date to NOW for:\n" + mail.get("title", "Unknown")
|
||||
add_child(confirm)
|
||||
confirm.popup_centered()
|
||||
confirm.confirmed.connect(func():
|
||||
var now_iso = Time.get_datetime_string_from_system(true)
|
||||
var res = await _rpc("admin_update_mail", {
|
||||
"mail_id": mail_id,
|
||||
"type": mail_type,
|
||||
"target_user_id": target_uid,
|
||||
"end_date": now_iso
|
||||
})
|
||||
if res.has("success"):
|
||||
_set_status("Mail ended", CLR_STATUS_OK)
|
||||
await _load_mail()
|
||||
confirm.queue_free()
|
||||
)
|
||||
|
||||
func _on_delete_mail_server_pressed() -> void:
|
||||
var mail = _get_selected_mail()
|
||||
if mail.is_empty(): return
|
||||
|
||||
var mail_id: String = mail.get("id", "")
|
||||
var mail_type: String = mail.get("type", "global")
|
||||
var target_uid: String = mail.get("target_user_id", "")
|
||||
|
||||
var confirm := ConfirmationDialog.new()
|
||||
confirm.title = "PERMANENTLY Delete Mail?"
|
||||
confirm.dialog_text = "Remove from server storage:\n" + mail.get("title", "Unknown") + "\n\nThis cannot be undone!"
|
||||
add_child(confirm)
|
||||
confirm.popup_centered()
|
||||
confirm.confirmed.connect(func():
|
||||
var res = await _rpc("admin_delete_mail_server", {
|
||||
"mail_id": mail_id,
|
||||
"type": mail_type,
|
||||
"target_user_id": target_uid
|
||||
})
|
||||
if res.has("success"):
|
||||
_set_status("Mail deleted from server", CLR_STATUS_OK)
|
||||
await _load_mail()
|
||||
confirm.queue_free()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user