Add a Statistics tab with a card for every soundtrack
Part of #41. The top bar has Quiz and Statistics tabs instead of the Statistics button. The Statistics tab replaces the quiz below the top bar (the song keeps playing) and shows every soundtrack as a card with its plays, last played date and most and least played songs, loaded from the new GET /api/v1/statistics/soundtracks. The cards can be searched (with the Search popup's matching, now in SoundtrackSearch), filtered on played or never played, and sorted A-Z, Z-A, most or least played. Clicking a card opens a popup with all its songs. Screenshots of the other states only differ in the top bar, and the layout baseline is taken again with the new statistics states.
This commit is contained in:
1 parent
f7619baf34
commit
a21b6e9b22
30 files changed
+25379
-2306
No files matched your search
@@ -0,0 +1,139 @@
|
||||
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
|
||||
Reference in new issue
Block a user