Add question reporting and reference reviews
This commit is contained in:
@@ -6,6 +6,16 @@ const CORRECT_ADVANCE_DELAY_SECONDS := 1.25
|
||||
const TEXT_SCALE_MIN := 0.85
|
||||
const TEXT_SCALE_MAX := 1.35
|
||||
const TEXT_SCALE_STEP := 0.1
|
||||
const REPORT_DETAILS_MIN := 10
|
||||
const REPORT_DETAILS_MAX := 2000
|
||||
const REPORT_CATEGORIES := [
|
||||
{"id": "clarity", "label": "Clarity"},
|
||||
{"id": "medical_accuracy", "label": "Medical accuracy"},
|
||||
{"id": "answer_explanation", "label": "Answer / explanation"},
|
||||
{"id": "formatting_media", "label": "Formatting / media"},
|
||||
{"id": "technical_issue", "label": "Technical issue"},
|
||||
{"id": "other", "label": "Other"}
|
||||
]
|
||||
|
||||
var main: Control
|
||||
var choice_buttons: Array[Button] = []
|
||||
@@ -54,6 +64,20 @@ var feedback_fog: ColorRect
|
||||
var labs_overlay: Control
|
||||
var labs_panel: PanelContainer
|
||||
var labs_content: VBoxContainer
|
||||
var report_overlay: Control
|
||||
var report_panel: PanelContainer
|
||||
var report_form_content: VBoxContainer
|
||||
var report_success_content: VBoxContainer
|
||||
var report_category_group: ButtonGroup
|
||||
var report_category_buttons: Array[CheckBox] = []
|
||||
var report_details: TextEdit
|
||||
var report_char_count: Label
|
||||
var report_error_label: Label
|
||||
var report_submit_button: Button
|
||||
var report_cancel_button: Button
|
||||
var report_success_label: Label
|
||||
var report_is_submitting := false
|
||||
var report_question_id := ""
|
||||
var media_requests: Array[HTTPRequest] = []
|
||||
var red_fog_time := 0.0
|
||||
var green_fog_time := 0.0
|
||||
@@ -76,6 +100,10 @@ func _ready() -> void:
|
||||
QuestionBankService.ratings_changed.connect(_refresh_player_stats)
|
||||
if not AuthState.auth_changed.is_connected(_refresh_player_stats):
|
||||
AuthState.auth_changed.connect(_refresh_player_stats)
|
||||
if not QuestionBankService.question_report_submitted.is_connected(_on_question_report_submitted):
|
||||
QuestionBankService.question_report_submitted.connect(_on_question_report_submitted)
|
||||
if not QuestionBankService.question_report_failed.is_connected(_on_question_report_failed):
|
||||
QuestionBankService.question_report_failed.connect(_on_question_report_failed)
|
||||
|
||||
var top := HBoxContainer.new()
|
||||
top.add_theme_constant_override("separation", 12)
|
||||
@@ -156,6 +184,8 @@ func _ready() -> void:
|
||||
report_button.tooltip_text = "Report question"
|
||||
report_button.icon = _make_report_icon()
|
||||
report_button.custom_minimum_size = Vector2(44, 44)
|
||||
Style.style_chip(report_button, Style.CYAN)
|
||||
UISoundService.set_button_sound(report_button, "open")
|
||||
report_button.pressed.connect(_toggle_report_current_question)
|
||||
question_toolbar.add_child(report_button)
|
||||
|
||||
@@ -326,17 +356,23 @@ func _ready() -> void:
|
||||
add_child(feedback_fog)
|
||||
|
||||
_build_labs_modal()
|
||||
_build_report_modal()
|
||||
|
||||
|
||||
func on_show(_payload: Variant = null) -> void:
|
||||
_load_question()
|
||||
|
||||
|
||||
func on_hide() -> void:
|
||||
_hide_report_modal(true)
|
||||
_hide_labs_modal()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_update_answer_feedback(delta)
|
||||
if visible and BlockSessionService.block_started and BlockSessionService.session_kind == "core":
|
||||
_update_timer_bar()
|
||||
if visible and BlockSessionService.block_started and BlockSessionService.mode == "Timed":
|
||||
if visible and BlockSessionService.block_started and BlockSessionService.mode == "Timed" and (report_overlay == null or not report_overlay.visible):
|
||||
BlockSessionService.seconds_remaining = max(0.0, BlockSessionService.seconds_remaining - delta)
|
||||
var shown_seconds := int(ceil(BlockSessionService.seconds_remaining))
|
||||
var minutes := int(floor(float(shown_seconds) / 60.0))
|
||||
@@ -346,6 +382,10 @@ func _process(delta: float) -> void:
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if report_overlay != null and report_overlay.visible and event.is_action_pressed("ui_cancel"):
|
||||
_hide_report_modal()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if labs_overlay != null and labs_overlay.visible and event.is_action_pressed("ui_cancel"):
|
||||
_hide_labs_modal()
|
||||
get_viewport().set_input_as_handled()
|
||||
@@ -367,6 +407,7 @@ func _input(event: InputEvent) -> void:
|
||||
|
||||
|
||||
func _load_question() -> void:
|
||||
_hide_report_modal(true)
|
||||
_reset_wrong_feedback()
|
||||
_hide_labs_modal()
|
||||
eliminated.clear()
|
||||
@@ -785,11 +826,7 @@ func _toggle_report_current_question() -> void:
|
||||
var question_id := str(BlockSessionService.get_current_question().get("id", ""))
|
||||
if question_id == "":
|
||||
return
|
||||
if reported_question_ids.has(question_id):
|
||||
reported_question_ids.erase(question_id)
|
||||
else:
|
||||
reported_question_ids.append(question_id)
|
||||
_refresh_toolbar_state()
|
||||
_show_report_modal(question_id)
|
||||
|
||||
|
||||
func _refresh_toolbar_state() -> void:
|
||||
@@ -799,6 +836,7 @@ func _refresh_toolbar_state() -> void:
|
||||
if report_button != null:
|
||||
var question_id := str(BlockSessionService.get_current_question().get("id", ""))
|
||||
Style.style_chip(report_button, Style.RED if reported_question_ids.has(question_id) else Style.CYAN)
|
||||
report_button.tooltip_text = "Report submitted; report another issue" if reported_question_ids.has(question_id) else "Report question"
|
||||
|
||||
|
||||
func _refresh_player_stats() -> void:
|
||||
@@ -976,6 +1014,280 @@ func _on_media_loaded(
|
||||
status.visible = false
|
||||
|
||||
|
||||
func _build_report_modal() -> void:
|
||||
report_overlay = Control.new()
|
||||
report_overlay.visible = false
|
||||
report_overlay.top_level = true
|
||||
report_overlay.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
report_overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
report_overlay.gui_input.connect(_report_overlay_gui_input)
|
||||
add_child(report_overlay)
|
||||
|
||||
var dim := ColorRect.new()
|
||||
dim.color = Color(0.02, 0.03, 0.05, 0.78)
|
||||
dim.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
dim.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
report_overlay.add_child(dim)
|
||||
|
||||
var center := CenterContainer.new()
|
||||
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
report_overlay.add_child(center)
|
||||
|
||||
report_panel = PanelContainer.new()
|
||||
report_panel.custom_minimum_size = Vector2(700, 600)
|
||||
report_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
report_panel.add_theme_stylebox_override("panel", Style.panel_box(Color("172337"), Style.RED))
|
||||
center.add_child(report_panel)
|
||||
|
||||
var modal_body := VBoxContainer.new()
|
||||
modal_body.add_theme_constant_override("separation", 14)
|
||||
report_panel.add_child(modal_body)
|
||||
|
||||
var header := HBoxContainer.new()
|
||||
header.add_theme_constant_override("separation", 10)
|
||||
modal_body.add_child(header)
|
||||
|
||||
var title := Label.new()
|
||||
title.text = "Report question"
|
||||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_set_scaled_label(title, 26, Style.TEXT)
|
||||
header.add_child(title)
|
||||
|
||||
var close_button := Button.new()
|
||||
close_button.text = "X"
|
||||
close_button.tooltip_text = "Close report form"
|
||||
close_button.custom_minimum_size = Vector2(42, 38)
|
||||
Style.style_chip(close_button, Style.RED)
|
||||
UISoundService.set_button_sound(close_button, "close")
|
||||
close_button.pressed.connect(_hide_report_modal)
|
||||
header.add_child(close_button)
|
||||
|
||||
report_form_content = VBoxContainer.new()
|
||||
report_form_content.add_theme_constant_override("separation", 12)
|
||||
modal_body.add_child(report_form_content)
|
||||
|
||||
var category_label := Label.new()
|
||||
category_label.text = "What is the problem?"
|
||||
_set_scaled_label(category_label, 18, Style.CYAN)
|
||||
report_form_content.add_child(category_label)
|
||||
|
||||
var category_grid := GridContainer.new()
|
||||
category_grid.columns = 2
|
||||
category_grid.add_theme_constant_override("h_separation", 18)
|
||||
category_grid.add_theme_constant_override("v_separation", 8)
|
||||
report_form_content.add_child(category_grid)
|
||||
|
||||
report_category_group = ButtonGroup.new()
|
||||
report_category_group.allow_unpress = false
|
||||
for category in REPORT_CATEGORIES:
|
||||
var category_button := CheckBox.new()
|
||||
category_button.text = str(category["label"])
|
||||
category_button.set_meta("report_category_id", str(category["id"]))
|
||||
category_button.button_group = report_category_group
|
||||
category_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
category_button.custom_minimum_size = Vector2(0, 38)
|
||||
_set_scaled_button_font(category_button, 16)
|
||||
category_button.toggled.connect(_on_report_category_toggled)
|
||||
category_grid.add_child(category_button)
|
||||
report_category_buttons.append(category_button)
|
||||
|
||||
var details_header := HBoxContainer.new()
|
||||
report_form_content.add_child(details_header)
|
||||
|
||||
var details_label := Label.new()
|
||||
details_label.text = "Details"
|
||||
details_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_set_scaled_label(details_label, 18, Style.CYAN)
|
||||
details_header.add_child(details_label)
|
||||
|
||||
report_char_count = Label.new()
|
||||
report_char_count.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
_set_scaled_label(report_char_count, 14, Style.MUTED)
|
||||
details_header.add_child(report_char_count)
|
||||
|
||||
report_details = TextEdit.new()
|
||||
report_details.placeholder_text = "Describe what is wrong and what should be reviewed."
|
||||
report_details.custom_minimum_size = Vector2(0, 190)
|
||||
report_details.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
report_details.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY
|
||||
report_details.add_theme_font_size_override("font_size", 16)
|
||||
report_details.add_theme_color_override("font_color", Style.TEXT)
|
||||
report_details.add_theme_color_override("font_placeholder_color", Style.MUTED)
|
||||
report_details.add_theme_stylebox_override("normal", Style.panel_box(Style.SURFACE_DARK, Color("3b4964")))
|
||||
report_details.text_changed.connect(_on_report_details_changed)
|
||||
report_form_content.add_child(report_details)
|
||||
|
||||
report_error_label = Label.new()
|
||||
report_error_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
report_error_label.custom_minimum_size = Vector2(0, 28)
|
||||
_set_scaled_label(report_error_label, 15, Style.RED)
|
||||
report_form_content.add_child(report_error_label)
|
||||
|
||||
var actions := HBoxContainer.new()
|
||||
actions.add_theme_constant_override("separation", 10)
|
||||
report_form_content.add_child(actions)
|
||||
|
||||
report_cancel_button = Button.new()
|
||||
report_cancel_button.text = "Cancel"
|
||||
report_cancel_button.custom_minimum_size = Vector2(130, 46)
|
||||
Style.style_chip(report_cancel_button, Style.MUTED)
|
||||
UISoundService.set_button_sound(report_cancel_button, "close")
|
||||
report_cancel_button.pressed.connect(_hide_report_modal)
|
||||
actions.add_child(report_cancel_button)
|
||||
|
||||
report_submit_button = Button.new()
|
||||
report_submit_button.text = "Submit report"
|
||||
report_submit_button.custom_minimum_size = Vector2(170, 46)
|
||||
report_submit_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
Style.style_primary_button(report_submit_button)
|
||||
UISoundService.set_button_sound(report_submit_button, "confirmation")
|
||||
report_submit_button.pressed.connect(_submit_question_report)
|
||||
actions.add_child(report_submit_button)
|
||||
|
||||
report_success_content = VBoxContainer.new()
|
||||
report_success_content.visible = false
|
||||
report_success_content.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
report_success_content.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
report_success_content.add_theme_constant_override("separation", 18)
|
||||
modal_body.add_child(report_success_content)
|
||||
|
||||
report_success_label = Label.new()
|
||||
report_success_label.text = "Report submitted"
|
||||
report_success_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
report_success_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_set_scaled_label(report_success_label, 24, Style.LIME)
|
||||
report_success_content.add_child(report_success_label)
|
||||
|
||||
var success_close_button := Button.new()
|
||||
success_close_button.text = "Close"
|
||||
success_close_button.custom_minimum_size = Vector2(180, 48)
|
||||
Style.style_primary_button(success_close_button)
|
||||
UISoundService.set_button_sound(success_close_button, "close")
|
||||
success_close_button.pressed.connect(_hide_report_modal)
|
||||
report_success_content.add_child(success_close_button)
|
||||
|
||||
_update_report_form_state()
|
||||
|
||||
|
||||
func _report_overlay_gui_input(event: InputEvent) -> void:
|
||||
var mouse_event := event as InputEventMouseButton
|
||||
if mouse_event == null or not mouse_event.pressed or mouse_event.button_index != MOUSE_BUTTON_LEFT:
|
||||
return
|
||||
if report_panel != null and not report_panel.get_global_rect().has_point(mouse_event.global_position):
|
||||
_hide_report_modal()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _show_report_modal(question_id: String) -> void:
|
||||
if report_overlay == null or report_is_submitting:
|
||||
return
|
||||
_hide_labs_modal()
|
||||
report_question_id = question_id
|
||||
report_form_content.visible = true
|
||||
report_success_content.visible = false
|
||||
report_details.text = ""
|
||||
report_error_label.text = ""
|
||||
for category_button in report_category_buttons:
|
||||
category_button.button_pressed = false
|
||||
_set_report_submitting(false)
|
||||
_update_report_form_state()
|
||||
report_overlay.visible = true
|
||||
report_overlay.move_to_front()
|
||||
BlockSessionService.pause_current_question_timer()
|
||||
|
||||
|
||||
func _hide_report_modal(force: bool = false) -> void:
|
||||
if report_overlay == null or (report_is_submitting and not force):
|
||||
return
|
||||
if report_overlay.visible:
|
||||
report_overlay.visible = false
|
||||
BlockSessionService.resume_current_question_timer()
|
||||
if not report_is_submitting:
|
||||
report_question_id = ""
|
||||
|
||||
|
||||
func _on_report_category_toggled(pressed: bool) -> void:
|
||||
if pressed:
|
||||
_update_report_form_state()
|
||||
|
||||
|
||||
func _on_report_details_changed() -> void:
|
||||
_update_report_form_state()
|
||||
|
||||
|
||||
func _selected_report_category() -> String:
|
||||
if report_category_group == null:
|
||||
return ""
|
||||
var selected := report_category_group.get_pressed_button()
|
||||
if selected == null:
|
||||
return ""
|
||||
return str(selected.get_meta("report_category_id", ""))
|
||||
|
||||
|
||||
func _update_report_form_state() -> void:
|
||||
if report_details == null or report_submit_button == null:
|
||||
return
|
||||
var details_length := report_details.text.strip_edges().length()
|
||||
report_char_count.text = "%d / %d" % [details_length, REPORT_DETAILS_MAX]
|
||||
report_char_count.add_theme_color_override("font_color", Style.RED if details_length > REPORT_DETAILS_MAX else Style.MUTED)
|
||||
if not report_is_submitting:
|
||||
report_error_label.text = ""
|
||||
report_submit_button.disabled = (
|
||||
report_is_submitting
|
||||
or _selected_report_category() == ""
|
||||
or details_length < REPORT_DETAILS_MIN
|
||||
or details_length > REPORT_DETAILS_MAX
|
||||
)
|
||||
|
||||
|
||||
func _set_report_submitting(value: bool) -> void:
|
||||
report_is_submitting = value
|
||||
if report_details != null:
|
||||
report_details.editable = not value
|
||||
for category_button in report_category_buttons:
|
||||
category_button.disabled = value
|
||||
if report_cancel_button != null:
|
||||
report_cancel_button.disabled = value
|
||||
if report_submit_button != null:
|
||||
report_submit_button.text = "Submitting..." if value else "Submit report"
|
||||
_update_report_form_state()
|
||||
|
||||
|
||||
func _submit_question_report() -> void:
|
||||
if report_is_submitting or report_question_id == "":
|
||||
return
|
||||
var category := _selected_report_category()
|
||||
var details := report_details.text.strip_edges()
|
||||
if category == "" or details.length() < REPORT_DETAILS_MIN or details.length() > REPORT_DETAILS_MAX:
|
||||
_update_report_form_state()
|
||||
return
|
||||
report_error_label.text = ""
|
||||
_set_report_submitting(true)
|
||||
QuestionBankService.submit_question_report(report_question_id, category, details)
|
||||
|
||||
|
||||
func _on_question_report_submitted(question_id: String, _report_id: String) -> void:
|
||||
if question_id != report_question_id:
|
||||
return
|
||||
_set_report_submitting(false)
|
||||
if not reported_question_ids.has(question_id):
|
||||
reported_question_ids.append(question_id)
|
||||
_refresh_toolbar_state()
|
||||
report_form_content.visible = false
|
||||
report_success_content.visible = true
|
||||
report_success_label.text = "Report submitted\nThank you for helping improve this question."
|
||||
UISoundService.play_sound("confirmation")
|
||||
|
||||
|
||||
func _on_question_report_failed(question_id: String, message: String) -> void:
|
||||
if question_id != report_question_id:
|
||||
return
|
||||
_set_report_submitting(false)
|
||||
report_error_label.text = message
|
||||
UISoundService.play_sound("error")
|
||||
|
||||
|
||||
func _build_labs_modal() -> void:
|
||||
labs_overlay = Control.new()
|
||||
labs_overlay.visible = false
|
||||
|
||||
Reference in New Issue
Block a user