Files
step3/scripts/ui/simulation_history.gd
T

156 lines
6.0 KiB
GDScript

extends VBoxContainer
const Style := preload("res://scripts/ui/ui_style.gd")
var main: Control
var list: VBoxContainer
var status_label: Label
var load_more_button: Button
var pending_simulation_id := ""
func _ready() -> void:
set_anchors_preset(Control.PRESET_FULL_RECT)
add_theme_constant_override("separation", 12)
if not ExamSimulationService.history_changed.is_connected(_render_history):
ExamSimulationService.history_changed.connect(_render_history)
if not ExamSimulationService.state_changed.is_connected(_on_simulation_loaded):
ExamSimulationService.state_changed.connect(_on_simulation_loaded)
if not ExamSimulationService.request_failed.is_connected(_on_request_failed):
ExamSimulationService.request_failed.connect(_on_request_failed)
var top := HBoxContainer.new()
add_child(top)
var back := Button.new()
back.text = "< Profile"
Style.style_chip(back, Style.CYAN)
back.pressed.connect(func(): main.show_screen("profile"))
top.add_child(back)
var title := Label.new()
title.text = "Simulation History"
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
Style.set_label(title, 30, Style.TEXT)
top.add_child(title)
var balance := Control.new()
balance.custom_minimum_size = Vector2(100, 0)
top.add_child(balance)
status_label = Label.new()
status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
Style.set_label(status_label, 16, Style.MUTED)
add_child(status_label)
var scroll := ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
add_child(scroll)
list = VBoxContainer.new()
list.size_flags_horizontal = Control.SIZE_EXPAND_FILL
list.add_theme_constant_override("separation", 8)
scroll.add_child(list)
load_more_button = Button.new()
load_more_button.text = "Load More"
load_more_button.visible = false
load_more_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
Style.style_chip(load_more_button, Style.CYAN)
load_more_button.pressed.connect(_load_more)
add_child(load_more_button)
func on_show(payload: Variant = null) -> void:
pending_simulation_id = ""
if str(payload) == "preserve" and not ExamSimulationService.history.is_empty():
return
status_label.text = "Loading simulation history..."
load_more_button.disabled = true
ExamSimulationService.load_history()
func _render_history() -> void:
if not visible:
return
for child in list.get_children():
child.queue_free()
if ExamSimulationService.history.is_empty():
status_label.text = "No simulations yet."
else:
status_label.text = "Select a completed simulation to review it, or resume an active simulation."
for raw_summary in ExamSimulationService.history:
var summary: Dictionary = raw_summary
var state := str(summary.get("state", ""))
var action := _action_for_state(state)
var button := Button.new()
button.text = _summary_text(summary)
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.custom_minimum_size = Vector2(0, 70)
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button.disabled = action == "none"
Style.style_chip(button, _state_color(state))
if action != "none":
button.pressed.connect(_open_simulation.bind(str(summary.get("id", ""))))
list.add_child(button)
load_more_button.visible = not ExamSimulationService.history_next_before.is_empty()
load_more_button.disabled = false
func _summary_text(summary: Dictionary) -> String:
var state := str(summary.get("state", ""))
var date := str(summary.get("createdUtc", "")).left(10)
if state == "completed":
return "%s | Completed\nPredicted %d (%d-%d) | %d/%d correct" % [date, int(summary.get("predictedScore", 0)), int(summary.get("rangeLow", 0)), int(summary.get("rangeHigh", 0)), int(summary.get("correct", 0)), int(summary.get("total", 0))]
if state == "block_active":
return "%s | Active\nResume Block %d of %d (timer continues)" % [date, int(summary.get("currentBlock", 0)) + 1, int(summary.get("totalBlocks", 12))]
if state == "between_blocks":
return "%s | In progress\n%d of %d blocks complete" % [date, int(summary.get("currentBlock", -1)) + 1, int(summary.get("totalBlocks", 12))]
return "%s | Abandoned\nThis simulation cannot be resumed." % date
func _open_simulation(simulation_id: String) -> void:
pending_simulation_id = simulation_id
status_label.text = "Opening simulation..."
ExamSimulationService.load_simulation(simulation_id)
func _on_simulation_loaded() -> void:
if not visible or pending_simulation_id.is_empty() or ExamSimulationService.simulation.is_empty():
return
pending_simulation_id = ""
var state := str(ExamSimulationService.simulation.get("state", ""))
if state == "completed":
main.show_screen("simulation_results", {"simulation": ExamSimulationService.simulation, "return_screen": "simulation_history"})
elif state == "block_active":
BlockSessionService.start_simulation_block(
ExamSimulationService.get_current_questions(),
int(ExamSimulationService.simulation.get("currentBlock", 0)),
int(ExamSimulationService.simulation.get("totalBlocks", 12)),
ExamSimulationService.get_block_deadline_unix())
BlockSessionService.restore_simulation_responses(ExamSimulationService.simulation.get("responses", []))
main.show_screen("player")
elif state == "between_blocks":
main.show_screen("simulation_break")
else:
status_label.text = "This simulation can no longer be resumed."
func _load_more() -> void:
load_more_button.disabled = true
ExamSimulationService.load_history(ExamSimulationService.history_next_before)
func _on_request_failed(message: String) -> void:
if not visible:
return
pending_simulation_id = ""
status_label.text = message
load_more_button.disabled = false
static func _action_for_state(state: String) -> String:
if state == "completed": return "results"
if state == "block_active" or state == "between_blocks": return "resume"
return "none"
static func _state_color(state: String) -> Color:
if state == "completed": return Style.LIME
if state == "block_active" or state == "between_blocks": return Style.CYAN
return Style.MUTED