247 lines
9.4 KiB
GDScript
247 lines
9.4 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 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"]
|