first commit
This commit is contained in:
96
godot_client/scripts/character/character_card.gd
Normal file
96
godot_client/scripts/character/character_card.gd
Normal file
@@ -0,0 +1,96 @@
|
||||
extends PanelContainer
|
||||
class_name CharacterCard
|
||||
## Character Card Component
|
||||
##
|
||||
## Displays a single character's summary information with select/delete actions.
|
||||
|
||||
#region Signals
|
||||
signal selected(character_id: String)
|
||||
signal delete_requested(character_id: String)
|
||||
#endregion
|
||||
|
||||
#region Node References
|
||||
@onready var name_label: Label = $MarginContainer/HBoxContainer/InfoVBox/NameLabel
|
||||
@onready var class_label: Label = $MarginContainer/HBoxContainer/InfoVBox/ClassLabel
|
||||
@onready var level_label: Label = $MarginContainer/HBoxContainer/InfoVBox/LevelLabel
|
||||
@onready var gold_label: Label = $MarginContainer/HBoxContainer/InfoVBox/GoldLabel
|
||||
@onready var location_label: Label = $MarginContainer/HBoxContainer/InfoVBox/LocationLabel
|
||||
@onready var select_button: Button = $MarginContainer/HBoxContainer/ActionVBox/SelectButton
|
||||
@onready var delete_button: Button = $MarginContainer/HBoxContainer/ActionVBox/DeleteButton
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
var _character_id: String = ""
|
||||
var _character_data: Dictionary = {}
|
||||
var _pending_data: Dictionary = {} # Store data if set before _ready
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
func _ready() -> void:
|
||||
#_apply_style()
|
||||
select_button.pressed.connect(_on_select_pressed)
|
||||
delete_button.pressed.connect(_on_delete_pressed)
|
||||
|
||||
# Apply pending data if set before _ready
|
||||
if not _pending_data.is_empty():
|
||||
_apply_character_data(_pending_data)
|
||||
_pending_data = {}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
func set_character_data(data: Dictionary) -> void:
|
||||
"""Set character data and update display."""
|
||||
if not is_node_ready():
|
||||
# Store for later if called before _ready
|
||||
_pending_data = data
|
||||
return
|
||||
|
||||
_apply_character_data(data)
|
||||
|
||||
|
||||
func _apply_character_data(data: Dictionary) -> void:
|
||||
"""Actually apply the character data to UI elements."""
|
||||
_character_data = data
|
||||
_character_id = data.get("character_id", "")
|
||||
|
||||
name_label.text = data.get("name", "Unknown")
|
||||
class_label.text = data.get("class_name", "Unknown Class")
|
||||
level_label.text = "Level %d" % data.get("level", 1)
|
||||
gold_label.text = "%d Gold" % data.get("gold", 0)
|
||||
location_label.text = _format_location(data.get("current_location", ""))
|
||||
|
||||
|
||||
func get_character_id() -> String:
|
||||
return _character_id
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
#func _apply_style() -> void:
|
||||
#"""Apply card styling."""
|
||||
#var style = StyleBoxFlat.new()
|
||||
#style.bg_color = ThemeColors.BACKGROUND_CARD
|
||||
#style.border_width_all = 1
|
||||
#style.border_color = ThemeColors.BORDER_DEFAULT
|
||||
#style.corner_radius_all = 8
|
||||
#style.content_margin_left = 0
|
||||
#style.content_margin_right = 0
|
||||
#style.content_margin_top = 0
|
||||
#style.content_margin_bottom = 0
|
||||
#add_theme_stylebox_override("panel", style)
|
||||
|
||||
|
||||
func _format_location(location_id: String) -> String:
|
||||
"""Convert location_id to display name."""
|
||||
if location_id.is_empty():
|
||||
return "Unknown"
|
||||
return location_id.replace("_", " ").capitalize()
|
||||
#endregion
|
||||
|
||||
#region Signal Handlers
|
||||
func _on_select_pressed() -> void:
|
||||
selected.emit(_character_id)
|
||||
|
||||
|
||||
func _on_delete_pressed() -> void:
|
||||
delete_requested.emit(_character_id)
|
||||
#endregion
|
||||
1
godot_client/scripts/character/character_card.gd.uid
Normal file
1
godot_client/scripts/character/character_card.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://sxgrib8ck0wx
|
||||
200
godot_client/scripts/character/character_list.gd
Normal file
200
godot_client/scripts/character/character_list.gd
Normal file
@@ -0,0 +1,200 @@
|
||||
extends Control
|
||||
## Character List Screen
|
||||
##
|
||||
## Displays user's characters and allows selection or creation.
|
||||
## Fetches data from GET /api/v1/characters endpoint.
|
||||
|
||||
#region Signals
|
||||
signal character_selected(character_id: String)
|
||||
signal create_requested
|
||||
#endregion
|
||||
|
||||
#region Node References
|
||||
@onready var character_container: VBoxContainer = $VBoxContainer/CharacterScrollContainer/CharacterVBox
|
||||
@onready var loading_indicator: CenterContainer = $VBoxContainer/LoadingIndicator
|
||||
@onready var error_container: MarginContainer = $VBoxContainer/ErrorContainer
|
||||
@onready var error_label: Label = $VBoxContainer/ErrorContainer/ErrorLabel
|
||||
@onready var empty_state: CenterContainer = $EmptyState
|
||||
@onready var tier_label: Label = $VBoxContainer/Header/TierLabel
|
||||
@onready var create_button: Button = $VBoxContainer/ActionContainer/CreateButton
|
||||
@onready var refresh_button: Button = $VBoxContainer/ActionContainer/RefreshButton
|
||||
@onready var scroll_container: ScrollContainer = $VBoxContainer/CharacterScrollContainer
|
||||
@onready var empty_create_button: Button = $EmptyState/VBoxContainer/CreateFirstButton
|
||||
#endregion
|
||||
|
||||
#region Service References
|
||||
@onready var http_client: Node = get_node("/root/HTTPClient")
|
||||
@onready var state_manager: Node = get_node("/root/StateManager")
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
var _characters: Array = []
|
||||
var _is_loading: bool = false
|
||||
var _tier: String = "free"
|
||||
var _limit: int = 1
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
func _ready() -> void:
|
||||
print("[CharacterList] Initialized")
|
||||
_connect_signals()
|
||||
_hide_error()
|
||||
empty_state.visible = false
|
||||
load_characters()
|
||||
|
||||
|
||||
func _connect_signals() -> void:
|
||||
create_button.pressed.connect(_on_create_button_pressed)
|
||||
refresh_button.pressed.connect(_on_refresh_button_pressed)
|
||||
empty_create_button.pressed.connect(_on_create_button_pressed)
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
func load_characters() -> void:
|
||||
"""Fetch characters from API."""
|
||||
if _is_loading:
|
||||
return
|
||||
|
||||
print("[CharacterList] Loading characters...")
|
||||
_set_loading(true)
|
||||
_hide_error()
|
||||
|
||||
http_client.http_get(
|
||||
"/api/v1/characters",
|
||||
_on_characters_loaded,
|
||||
_on_characters_error
|
||||
)
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
"""Refresh character list."""
|
||||
load_characters()
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
func _set_loading(loading: bool) -> void:
|
||||
_is_loading = loading
|
||||
loading_indicator.visible = loading
|
||||
scroll_container.visible = not loading
|
||||
refresh_button.disabled = loading
|
||||
|
||||
|
||||
func _show_error(message: String) -> void:
|
||||
error_label.text = message
|
||||
error_container.visible = true
|
||||
|
||||
|
||||
func _hide_error() -> void:
|
||||
error_container.visible = false
|
||||
|
||||
|
||||
func _update_ui() -> void:
|
||||
# Update tier info
|
||||
tier_label.text = "%s: %d/%d" % [_tier.capitalize(), _characters.size(), _limit]
|
||||
|
||||
# Enable/disable create button based on limit
|
||||
create_button.disabled = _characters.size() >= _limit
|
||||
|
||||
# Show empty state or character list
|
||||
if _characters.is_empty():
|
||||
empty_state.visible = true
|
||||
scroll_container.visible = false
|
||||
else:
|
||||
empty_state.visible = false
|
||||
scroll_container.visible = true
|
||||
|
||||
|
||||
func _populate_character_list() -> void:
|
||||
# Clear existing cards
|
||||
for child in character_container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Create card for each character
|
||||
var card_scene = preload("res://scenes/character/character_card.tscn")
|
||||
|
||||
for char_data in _characters:
|
||||
var card = card_scene.instantiate()
|
||||
card.set_character_data(char_data)
|
||||
card.selected.connect(_on_character_card_selected)
|
||||
card.delete_requested.connect(_on_character_delete_requested)
|
||||
character_container.add_child(card)
|
||||
|
||||
print("[CharacterList] Populated %d character cards" % _characters.size())
|
||||
#endregion
|
||||
|
||||
#region Signal Handlers
|
||||
func _on_characters_loaded(response: APIResponse) -> void:
|
||||
_set_loading(false)
|
||||
|
||||
if not response.is_success():
|
||||
_on_characters_error(response)
|
||||
return
|
||||
|
||||
var result = response.result
|
||||
_characters = result.get("characters", [])
|
||||
_tier = result.get("tier", "free")
|
||||
_limit = result.get("limit", 1)
|
||||
|
||||
print("[CharacterList] Loaded %d characters (tier=%s, limit=%d)" % [_characters.size(), _tier, _limit])
|
||||
|
||||
_populate_character_list()
|
||||
_update_ui()
|
||||
|
||||
|
||||
func _on_characters_error(response: APIResponse) -> void:
|
||||
_set_loading(false)
|
||||
|
||||
var message = response.get_error_message()
|
||||
if message.is_empty():
|
||||
message = "Failed to load characters"
|
||||
|
||||
if response.status == 401:
|
||||
print("[CharacterList] Session expired, redirecting to login")
|
||||
get_tree().change_scene_to_file("res://scenes/auth/login.tscn")
|
||||
return
|
||||
|
||||
print("[CharacterList] Error: %s (status=%d)" % [message, response.status])
|
||||
_show_error(message)
|
||||
|
||||
|
||||
func _on_character_card_selected(character_id: String) -> void:
|
||||
print("[CharacterList] Character selected: %s" % character_id)
|
||||
state_manager.select_character(character_id)
|
||||
character_selected.emit(character_id)
|
||||
# TODO: Navigate to main game scene
|
||||
# get_tree().change_scene_to_file("res://scenes/game/main_game.tscn")
|
||||
|
||||
|
||||
func _on_character_delete_requested(character_id: String) -> void:
|
||||
print("[CharacterList] Delete requested for: %s" % character_id)
|
||||
# TODO: Show confirmation dialog, then call DELETE endpoint
|
||||
# For now, directly delete
|
||||
_delete_character(character_id)
|
||||
|
||||
|
||||
func _delete_character(character_id: String) -> void:
|
||||
"""Delete a character via API."""
|
||||
http_client.http_delete(
|
||||
"/api/v1/characters/%s" % character_id,
|
||||
func(response: APIResponse):
|
||||
if response.is_success():
|
||||
print("[CharacterList] Character deleted: %s" % character_id)
|
||||
load_characters() # Refresh list
|
||||
else:
|
||||
_show_error(response.get_error_message()),
|
||||
func(response: APIResponse):
|
||||
_show_error(response.get_error_message())
|
||||
)
|
||||
|
||||
|
||||
func _on_create_button_pressed() -> void:
|
||||
print("[CharacterList] Create button pressed")
|
||||
create_requested.emit()
|
||||
# TODO: Navigate to character creation wizard
|
||||
# get_tree().change_scene_to_file("res://scenes/character/character_create.tscn")
|
||||
|
||||
|
||||
func _on_refresh_button_pressed() -> void:
|
||||
print("[CharacterList] Refresh button pressed")
|
||||
refresh()
|
||||
#endregion
|
||||
1
godot_client/scripts/character/character_list.gd.uid
Normal file
1
godot_client/scripts/character/character_list.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://x4mt6jwbywsl
|
||||
Reference in New Issue
Block a user