Require API question loading
This commit is contained in:
@@ -1,47 +1,33 @@
|
||||
extends Node
|
||||
|
||||
const QUESTION_FILE := "res://data/questions/sample_questions.json"
|
||||
const DEFAULT_QUESTIONS_API_URL := "https://s3q.ranaze.com/api/questions/search"
|
||||
|
||||
signal questions_changed
|
||||
signal questions_load_failed(message: String)
|
||||
|
||||
var questions: Array = []
|
||||
var request: HTTPRequest
|
||||
var questions_api_url := DEFAULT_QUESTIONS_API_URL
|
||||
var is_loading := false
|
||||
var last_error := ""
|
||||
|
||||
func _ready() -> void:
|
||||
questions_api_url = str(ProjectSettings.get_setting("step3/questions_api_url", DEFAULT_QUESTIONS_API_URL))
|
||||
request = HTTPRequest.new()
|
||||
request.request_completed.connect(_on_api_questions_loaded)
|
||||
add_child(request)
|
||||
load_questions()
|
||||
|
||||
|
||||
func load_questions() -> void:
|
||||
questions.clear()
|
||||
var file := FileAccess.open(QUESTION_FILE, FileAccess.READ)
|
||||
if file == null:
|
||||
push_error("Unable to open question file: %s" % QUESTION_FILE)
|
||||
return
|
||||
|
||||
var parsed = JSON.parse_string(file.get_as_text())
|
||||
if typeof(parsed) != TYPE_ARRAY:
|
||||
push_error("Question file must contain an array.")
|
||||
return
|
||||
|
||||
for item in parsed:
|
||||
if _is_valid_question(item):
|
||||
questions.append(item)
|
||||
else:
|
||||
push_warning("Skipping invalid question: %s" % str(item.get("id", "unknown")))
|
||||
|
||||
questions_changed.emit()
|
||||
|
||||
|
||||
func load_questions_from_api() -> void:
|
||||
if AuthState.access_token.is_empty():
|
||||
_set_load_error("Login required before loading questions.")
|
||||
return
|
||||
|
||||
is_loading = true
|
||||
last_error = ""
|
||||
questions.clear()
|
||||
questions_changed.emit()
|
||||
|
||||
var headers := PackedStringArray([
|
||||
"Content-Type: application/json",
|
||||
"Authorization: Bearer %s" % AuthState.access_token
|
||||
@@ -51,17 +37,18 @@ func load_questions_from_api() -> void:
|
||||
}
|
||||
var err := request.request(questions_api_url, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
|
||||
if err != OK:
|
||||
push_warning("Failed to request questions API. Using local question bank.")
|
||||
_set_load_error("Failed to request questions API.")
|
||||
|
||||
|
||||
func _on_api_questions_loaded(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
is_loading = false
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
||||
push_warning("Questions API request failed. Using local question bank.")
|
||||
_set_load_error("Questions API request failed with status %d." % response_code)
|
||||
return
|
||||
|
||||
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
||||
if typeof(parsed) != TYPE_ARRAY:
|
||||
push_warning("Questions API response was not an array. Using local question bank.")
|
||||
_set_load_error("Questions API response was not an array.")
|
||||
return
|
||||
|
||||
var api_questions: Array = []
|
||||
@@ -71,10 +58,11 @@ func _on_api_questions_loaded(result: int, response_code: int, _headers: PackedS
|
||||
api_questions.append(normalized)
|
||||
|
||||
if api_questions.is_empty():
|
||||
push_warning("Questions API returned no usable questions. Using local question bank.")
|
||||
_set_load_error("Questions API returned no usable questions.")
|
||||
return
|
||||
|
||||
questions = api_questions
|
||||
last_error = ""
|
||||
questions_changed.emit()
|
||||
|
||||
|
||||
@@ -109,6 +97,18 @@ func build_block(system_name: String, block_size: int) -> Array:
|
||||
return pool.slice(0, min(block_size, pool.size()))
|
||||
|
||||
|
||||
func has_questions() -> bool:
|
||||
return not questions.is_empty()
|
||||
|
||||
|
||||
func _set_load_error(message: String) -> void:
|
||||
is_loading = false
|
||||
last_error = message
|
||||
push_warning(message)
|
||||
questions_load_failed.emit(message)
|
||||
questions_changed.emit()
|
||||
|
||||
|
||||
func _is_valid_question(item: Variant) -> bool:
|
||||
if typeof(item) != TYPE_DICTIONARY:
|
||||
return false
|
||||
|
||||
@@ -7,6 +7,8 @@ var selected_system := "All Systems"
|
||||
var system_label: Label
|
||||
var size_spin: SpinBox
|
||||
var mode_option: OptionButton
|
||||
var status_label: Label
|
||||
var start_button: Button
|
||||
|
||||
func _ready() -> void:
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
@@ -49,12 +51,17 @@ func _ready() -> void:
|
||||
Style.set_label(note, 16, Style.MUTED)
|
||||
add_child(note)
|
||||
|
||||
var start := Button.new()
|
||||
start.text = "Launch Block"
|
||||
start.custom_minimum_size = Vector2(220, 54)
|
||||
Style.style_primary_button(start)
|
||||
start.pressed.connect(_start_block)
|
||||
add_child(start)
|
||||
status_label = Label.new()
|
||||
status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
Style.set_label(status_label, 16, Style.RED)
|
||||
add_child(status_label)
|
||||
|
||||
start_button = Button.new()
|
||||
start_button.text = "Launch Block"
|
||||
start_button.custom_minimum_size = Vector2(220, 54)
|
||||
Style.style_primary_button(start_button)
|
||||
start_button.pressed.connect(_start_block)
|
||||
add_child(start_button)
|
||||
|
||||
|
||||
func on_show(payload: Variant = null) -> void:
|
||||
@@ -63,8 +70,13 @@ func on_show(payload: Variant = null) -> void:
|
||||
var available := QuestionBankService.get_questions_for_system(selected_system).size()
|
||||
size_spin.max_value = max(1, available)
|
||||
size_spin.value = min(size_spin.value, available)
|
||||
start_button.disabled = available == 0
|
||||
status_label.text = QuestionBankService.last_error if available == 0 else ""
|
||||
|
||||
|
||||
func _start_block() -> void:
|
||||
if not QuestionBankService.has_questions():
|
||||
status_label.text = "Questions have not loaded from the API yet."
|
||||
return
|
||||
BlockSessionService.start_block(selected_system, int(size_spin.value), mode_option.get_item_text(mode_option.selected))
|
||||
main.show_screen("player")
|
||||
|
||||
@@ -8,6 +8,8 @@ var zone_list: GridContainer
|
||||
func _ready() -> void:
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_theme_constant_override("separation", 14)
|
||||
if not QuestionBankService.questions_changed.is_connected(_refresh_zones):
|
||||
QuestionBankService.questions_changed.connect(_refresh_zones)
|
||||
|
||||
var title := Label.new()
|
||||
title.text = "Study Map"
|
||||
@@ -27,9 +29,31 @@ func _ready() -> void:
|
||||
|
||||
|
||||
func on_show(_payload: Variant = null) -> void:
|
||||
_refresh_zones()
|
||||
|
||||
|
||||
func _refresh_zones() -> void:
|
||||
if zone_list == null:
|
||||
return
|
||||
|
||||
for child in zone_list.get_children():
|
||||
child.queue_free()
|
||||
|
||||
if QuestionBankService.is_loading:
|
||||
var loading := Label.new()
|
||||
loading.text = "Loading question bank..."
|
||||
Style.set_label(loading, 20, Style.AMBER)
|
||||
zone_list.add_child(loading)
|
||||
return
|
||||
|
||||
if not QuestionBankService.has_questions():
|
||||
var empty := Label.new()
|
||||
empty.text = QuestionBankService.last_error if QuestionBankService.last_error != "" else "No API questions loaded."
|
||||
empty.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
Style.set_label(empty, 20, Style.RED)
|
||||
zone_list.add_child(empty)
|
||||
return
|
||||
|
||||
var systems: Array[String] = ["All Systems"]
|
||||
systems.append_array(QuestionBankService.get_systems())
|
||||
var stats: Dictionary = ProfileService.get_profile().get("system_stats", {})
|
||||
|
||||
Reference in New Issue
Block a user