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 launch_button: Button var readiness_label: Label var pending_simulation_id := "" var pending_launch := false 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.readiness_changed.is_connected(_refresh_readiness): ExamSimulationService.readiness_changed.connect(_refresh_readiness) 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) var launch_panel := PanelContainer.new() launch_panel.add_theme_stylebox_override("panel", Style.panel_box(Color("07192c"), Style.CYAN)) add_child(launch_panel) var launch_content := HBoxContainer.new() launch_content.add_theme_constant_override("separation", 18) launch_panel.add_child(launch_content) var launch_copy := VBoxContainer.new() launch_copy.size_flags_horizontal = Control.SIZE_EXPAND_FILL launch_copy.add_theme_constant_override("separation", 5) launch_content.add_child(launch_copy) var launch_title := Label.new() launch_title.text = "Step 3 Day 1 Simulation" Style.set_label(launch_title, 22, Style.TEXT) launch_copy.add_child(launch_title) readiness_label = Label.new() readiness_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Style.set_label(readiness_label, 15, Style.MUTED) launch_copy.add_child(readiness_label) launch_button = Button.new() launch_button.text = "Checking readiness..." launch_button.custom_minimum_size = Vector2(210, 48) launch_button.size_flags_vertical = Control.SIZE_SHRINK_CENTER launch_button.disabled = true Style.style_primary_button(launch_button) launch_button.pressed.connect(_launch_or_resume) launch_content.add_child(launch_button) 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 = "" pending_launch = false _refresh_readiness() ExamSimulationService.load_readiness() 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 | %sCompleted\nPredicted %d (%d-%d) | %d/%d correct" % [date, "Sample | " if bool(summary.get("isSample", false)) else "", 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_launch = false pending_simulation_id = simulation_id status_label.text = "Opening simulation..." ExamSimulationService.load_simulation(simulation_id) func _on_simulation_loaded() -> void: if not visible or ExamSimulationService.simulation.is_empty(): return if pending_launch: pending_launch = false _route_loaded_simulation() return if pending_simulation_id.is_empty(): return pending_simulation_id = "" _route_loaded_simulation() func _route_loaded_simulation() -> void: 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." _refresh_readiness() func _launch_or_resume() -> void: pending_launch = true pending_simulation_id = "" launch_button.disabled = true readiness_label.text = "Opening your simulation..." if _has_active_simulation(): ExamSimulationService.load_active() else: ExamSimulationService.create_simulation() func _refresh_readiness() -> void: if launch_button == null: return var simulation_ready := bool(ExamSimulationService.readiness.get("ready", false)) var active := _has_active_simulation() var available := int(ExamSimulationService.readiness.get("availableQuestions", 0)) launch_button.text = "Resume Simulation" if active else "Start Simulation" launch_button.disabled = pending_launch or (not simulation_ready and not active) if pending_launch: readiness_label.text = "Opening your simulation..." elif active: readiness_label.text = "Your progress is saved between blocks. Resume where you left off." elif simulation_ready: readiness_label.text = "Full-length, 12-block simulation with a provisional predicted score." else: readiness_label.text = "Unlocks when 232 published questions are available. Currently %d/232." % available func _has_active_simulation() -> bool: return has_active_simulation( ExamSimulationService.readiness, ExamSimulationService.simulation) 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_launch = false pending_simulation_id = "" status_label.text = message load_more_button.disabled = false _refresh_readiness() 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 static func has_active_simulation(readiness: Dictionary, simulation: Dictionary) -> bool: if bool(readiness.get("hasActiveSimulation", false)): return true if simulation.is_empty(): return false return str(simulation.get("state", "")) in ["between_blocks", "block_active"]