class_name StatisticsView extends Panel ## Shows every soundtrack on the server as a card with its play statistics. The cards ## can be searched, filtered on whether they have been played, and sorted. ## ## Shown instead of the quiz when the Statistics tab is chosen. ## Emitted when a card is clicked, to show all songs of [param soundtrack]. signal soundtrack_selected(soundtrack: SoundtrackWithSongs) ## The orders the cards can be sorted in, in the order of the sort menu. enum SortOrder { A_TO_Z, Z_TO_A, MOST_PLAYED, LEAST_PLAYED } ## Which soundtracks are shown, in the order of the show menu. enum ShowFilter { ALL, PLAYED, NEVER_PLAYED } const CARD_SCENE: PackedScene = preload("res://statistics/SoundtrackCard.tscn") ## The cards in the order they are shown, hidden ones included. var cards: Array[SoundtrackCard] = [] var _search: SoundtrackSearch = SoundtrackSearch.new() var _loading: bool = false @onready var search_field: LineEdit = %SearchField @onready var clear_button: Button = %ClearButton @onready var show_option: OptionButton = %ShowOption @onready var sort_option: OptionButton = %SortOption @onready var count_label: Label = %CountLabel @onready var status_label: Label = %StatusLabel @onready var retry_button: Button = %RetryButton @onready var card_grid: GridContainer = %CardGrid func _ready() -> void: search_field.text_changed.connect(func(_text: String) -> void: apply_filter()) clear_button.pressed.connect(clear_search) show_option.item_selected.connect(func(_index: int) -> void: apply_filter()) sort_option.item_selected.connect(func(_index: int) -> void: apply_sort()) retry_button.pressed.connect(refresh) retry_button.visible = false ## Loads the statistics from the server. The cards already shown stay until the new ## ones arrive. func refresh() -> void: if _loading: return _loading = true retry_button.visible = false if cards.is_empty(): status_label.text = "Loading statistics…" MusicServerClient.fetch_soundtrack_statistics(_on_loaded, _on_load_failed) ## Shows every card again, keeping the show filter and the sort order. func clear_search() -> void: search_field.text = "" apply_filter() ## Puts the cards in the order chosen in the sort menu. func apply_sort() -> void: var order: SortOrder = sort_option.selected as SortOrder cards.sort_custom(func(a: SoundtrackCard, b: SoundtrackCard) -> bool: return _comes_before(a.soundtrack, b.soundtrack, order)) for i: int in cards.size(): card_grid.move_child(cards[i], i) ## Shows only the cards that match the search text and the show menu. func apply_filter() -> void: var search_text: String = search_field.text.strip_edges() var matching: Dictionary = {} for soundtrack_name: String in _search.find_matches(search_text) \ if !search_text.is_empty() else []: matching[soundtrack_name] = true var filter: ShowFilter = show_option.selected as ShowFilter var shown: int = 0 for card: SoundtrackCard in cards: card.visible = (search_text.is_empty() || matching.has(card.soundtrack.soundtrack_name)) \ && _passes_filter(card.soundtrack, filter) if card.visible: shown += 1 if shown == cards.size(): count_label.text = "%d soundtracks" % cards.size() else: count_label.text = "%d of %d soundtracks" % [shown, cards.size()] func _on_loaded(soundtracks: Array[SoundtrackWithSongs]) -> void: _loading = false status_label.text = "" # Cards are reused, so opening the tab again only updates their text while cards.size() < soundtracks.size(): var card: SoundtrackCard = CARD_SCENE.instantiate() card_grid.add_child(card) card.pressed.connect(soundtrack_selected.emit) cards.append(card) while cards.size() > soundtracks.size(): cards.pop_back().free() var names: Array[String] = [] for i: int in soundtracks.size(): cards[i].show_soundtrack(soundtracks[i]) names.append(soundtracks[i].soundtrack_name) _search.set_names(names) apply_sort() apply_filter() func _on_load_failed() -> void: _loading = false if cards.is_empty(): status_label.text = "Could not load statistics" retry_button.visible = true static func _passes_filter(soundtrack: SoundtrackWithSongs, filter: ShowFilter) -> bool: match filter: ShowFilter.PLAYED: return soundtrack.soundtrack_played > 0 ShowFilter.NEVER_PLAYED: return soundtrack.soundtrack_played == 0 return true static func _comes_before( a: SoundtrackWithSongs, b: SoundtrackWithSongs, order: SortOrder) -> bool: var by_name: int = a.soundtrack_name.naturalnocasecmp_to(b.soundtrack_name) match order: SortOrder.Z_TO_A: return by_name > 0 SortOrder.MOST_PLAYED: if a.soundtrack_played != b.soundtrack_played: return a.soundtrack_played > b.soundtrack_played SortOrder.LEAST_PLAYED: if a.soundtrack_played != b.soundtrack_played: return a.soundtrack_played < b.soundtrack_played return by_name < 0