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