class_name SoundtrackStatsPopup extends BasePopup ## Shows a soundtrack's cover, play statistics and alternative names, and every song in ## it, most played first. Opened by clicking a card in the statistics view. ## ## Edit names opens an editor for the alternative names in place of the song list. Saving ## sends them to the server, which search then finds the soundtrack by. ## Emitted when the alternative names of [param soundtrack] have been saved. signal alternative_names_changed(soundtrack: Soundtrack) ## The covers on disk, shared with the statistics view. No cover is shown without it. var covers: CoverCache ## The soundtrack shown. var soundtrack: SoundtrackWithSongs # The names in the editor, saved only when Save is pressed var _edited_names: Array[String] = [] @onready var title_label: Label = %TitleLabel @onready var summary_label: Label = %SummaryLabel @onready var cover_picture: TextureRect = %CoverPicture @onready var cover_placeholder: Label = %CoverPlaceholder @onready var names_label: Label = %NamesLabel @onready var edit_names_button: Button = %EditNamesButton @onready var song_scroll: ScrollContainer = %ScrollContainer @onready var song_list: VBoxContainer = %SongList @onready var names_editor: VBoxContainer = %NamesEditor @onready var editor_names: VBoxContainer = %EditorNames @onready var new_name_field: LineEdit = %NewNameField @onready var add_name_button: Button = %AddNameButton @onready var editor_status: Label = %EditorStatus @onready var save_names_button: Button = %SaveNamesButton @onready var cancel_names_button: Button = %CancelNamesButton func _ready() -> void: edit_names_button.pressed.connect(edit_names) add_name_button.pressed.connect(add_name) new_name_field.text_submitted.connect(func(_text: String) -> void: add_name()) save_names_button.pressed.connect(save_names) cancel_names_button.pressed.connect(stop_editing) ## Shows [param shown] and opens the popup. func show_soundtrack(shown: SoundtrackWithSongs) -> void: soundtrack = shown stop_editing() title_label.text = shown.soundtrack_name var summary: PackedStringArray = [SoundtrackCard.played_text(shown.soundtrack_played)] var last_played: String = SoundtrackCard.last_played_text(shown.soundtrack_last_played) if !last_played.is_empty(): summary.append(last_played) summary.append("%d %s" % [shown.songs.size(), "song" if shown.songs.size() == 1 else "songs"]) summary_label.text = " · ".join(summary) _show_names() cover_placeholder.text = SoundtrackCard.initials(shown.soundtrack_name) _show_cover(null) if covers != null: covers.fetch_cover(shown.soundtrack_uuid, shown.cover_modified, _show_cover_of.bind(shown.soundtrack_uuid)) Settings.delete_children(song_list) var songs: Array[SongInfoForStats] = SoundtrackCard.songs_by_plays(shown.songs) for row: int in songs.size(): song_list.add_child(ListRow.create(SoundtrackCard.song_text(songs[row]), row, &"TextLarge")) open() ## Opens the editor for the alternative names, in place of the song list. func edit_names() -> void: _edited_names.assign(soundtrack.alternative_names) new_name_field.text = "" editor_status.visible = false _show_edited_names() names_editor.visible = true song_scroll.visible = false edit_names_button.disabled = true new_name_field.grab_focus() ## Closes the editor without saving. func stop_editing() -> void: names_editor.visible = false song_scroll.visible = true edit_names_button.disabled = false _set_saving(false) ## Adds the name in the field to the edited names, unless it is empty, the soundtrack's ## own name or already there. func add_name() -> void: var new_name: String = new_name_field.text.strip_edges() if new_name.is_empty() || new_name.to_lower() == soundtrack.soundtrack_name.to_lower() \ || _edited_names.any(func(name: String) -> bool: return name.to_lower() == new_name.to_lower()): return _edited_names.append(new_name) new_name_field.text = "" _show_edited_names() ## Takes [param removed] out of the edited names. func remove_name(removed: String) -> void: _edited_names.erase(removed) _show_edited_names() ## Sends the edited names to the server. The editor closes when they are saved. func save_names() -> void: # A name typed but not added yet is meant to be saved too add_name() _set_saving(true) MusicServerClient.set_alternative_names(soundtrack.soundtrack_uuid, _edited_names, _on_names_saved, _on_names_not_saved) func _on_names_saved(updated: Soundtrack) -> void: soundtrack.alternative_names = updated.alternative_names _show_names() stop_editing() alternative_names_changed.emit(updated) func _on_names_not_saved() -> void: _set_saving(false) editor_status.text = "Could not save the names. Try again." editor_status.visible = true func _set_saving(saving: bool) -> void: save_names_button.disabled = saving cancel_names_button.disabled = saving save_names_button.text = "Saving…" if saving else "Save" func _show_names() -> void: var names: Array[String] = soundtrack.alternative_names names_label.text = ", ".join(names) if !names.is_empty() else "No other names yet" func _show_edited_names() -> void: Settings.delete_children(editor_names) for row: int in _edited_names.size(): var line: HBoxContainer = HBoxContainer.new() var label: Label = Label.new() label.text = _edited_names[row] label.size_flags_horizontal = Control.SIZE_EXPAND_FILL label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(label) var remove: Button = Button.new() remove.text = "Remove" remove.focus_mode = Control.FOCUS_NONE remove.pressed.connect(remove_name.bind(_edited_names[row])) line.add_child(remove) var list_row: PanelContainer = PanelContainer.new() list_row.theme_type_variation = &"ListRowOdd" if row % 2 != 0 else &"ListRowEven" list_row.add_child(line) editor_names.add_child(list_row) func _show_cover(cover: Image) -> void: cover_picture.texture = ImageTexture.create_from_image(cover) if cover != null else null cover_placeholder.visible = cover == null # Covers arrive later; one for a soundtrack no longer shown is left out func _show_cover_of(cover: Image, uuid: String) -> void: if soundtrack != null && soundtrack.soundtrack_uuid == uuid: _show_cover(cover)