diff --git a/sublime/adapters/filesystem/adapter.py b/sublime/adapters/filesystem/adapter.py
index c6adbaa..1aae012 100644
--- a/sublime/adapters/filesystem/adapter.py
+++ b/sublime/adapters/filesystem/adapter.py
@@ -515,7 +515,7 @@ class FilesystemAdapter(CachingAdapter):
"year": getattr(api_album, "year", None),
"genre": ingest_genre_data(g) if (g := api_album.genre) else None,
"artist": ingest_artist_data(ar) if (ar := api_album.artist) else None,
- "songs": [
+ "_songs": [
ingest_song_data(s, fill_album=False) for s in api_album.songs or []
],
"_cover_art": self._do_ingest_new_data(
diff --git a/sublime/adapters/filesystem/models.py b/sublime/adapters/filesystem/models.py
index c7ceb34..524c303 100644
--- a/sublime/adapters/filesystem/models.py
+++ b/sublime/adapters/filesystem/models.py
@@ -108,6 +108,15 @@ class Album(BaseModel):
except Exception:
return None
+ @property
+ def songs(self) -> List["Song"]:
+ albums = Album.select()
+ artists = Album.select()
+ return sorted(
+ prefetch(self._songs, albums, artists),
+ key=lambda s: (s.disc_number or 1, s.track),
+ )
+
class AlbumQueryResult(BaseModel):
query_hash = TextField(primary_key=True)
@@ -144,7 +153,7 @@ class Song(BaseModel):
duration = DurationField(null=True)
parent_id = TextField(null=True)
- album = ForeignKeyField(Album, null=True, backref="songs")
+ album = ForeignKeyField(Album, null=True, backref="_songs")
artist = ForeignKeyField(Artist, null=True)
genre = ForeignKeyField(Genre, null=True, backref="songs")
diff --git a/sublime/ui/app_styles.css b/sublime/ui/app_styles.css
index 78dab28..d8cb2d7 100644
--- a/sublime/ui/app_styles.css
+++ b/sublime/ui/app_styles.css
@@ -64,12 +64,17 @@
margin: 10px 15px 0 10px;
}
+#artist-info-panel {
+ margin-bottom: 10px;
+}
+
#playlist-name, #artist-detail-panel #artist-name {
font-size: 40px;
margin-bottom: 10px;
}
-#playlist-name.collapsed {
+#playlist-name.collapsed,
+#artist-detail-panel #artist-name.collapsed {
font-size: 30px;
}
@@ -204,9 +209,7 @@
}
#artist-album-artwork {
- margin: 10px;
- min-width: 300px;
- min-height: 300px;
+ margin: 10px 15px 0 10px;
}
#artist-album-list-artwork {
diff --git a/sublime/ui/artists.py b/sublime/ui/artists.py
index ffd480d..22049c2 100644
--- a/sublime/ui/artists.py
+++ b/sublime/ui/artists.py
@@ -1,6 +1,6 @@
from datetime import timedelta
from random import randint
-from typing import Any, List, Sequence
+from typing import List, Sequence
from gi.repository import Gio, GLib, GObject, Gtk, Pango
@@ -36,6 +36,9 @@ class ArtistsPanel(Gtk.Paned):
self.artist_detail_panel.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
)
+ self.artist_detail_panel.connect(
+ "refresh-window", lambda _, *args: self.emit("refresh-window", *args),
+ )
self.pack2(self.artist_detail_panel, True, False)
def update(self, app_config: AppConfiguration, force: bool = False):
@@ -145,7 +148,7 @@ class ArtistList(Gtk.Box):
self.loading_indicator.hide()
-class ArtistDetailPanel(Gtk.ScrolledWindow):
+class ArtistDetailPanel(Gtk.Box):
"""Defines the artists list."""
__gsignals__ = {
@@ -154,19 +157,30 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
GObject.TYPE_NONE,
(int, object, object),
),
+ "refresh-window": (
+ GObject.SignalFlags.RUN_FIRST,
+ GObject.TYPE_NONE,
+ (object, bool),
+ ),
}
update_order_token = 0
+ artist_details_expanded = False
def __init__(self, *args, **kwargs):
- super().__init__(*args, name="artist-detail-panel", **kwargs)
+ super().__init__(
+ *args,
+ name="artist-detail-panel",
+ orientation=Gtk.Orientation.VERTICAL,
+ **kwargs,
+ )
self.albums: Sequence[API.Album] = []
self.artist_id = None
- artist_info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-
# Artist info panel
- self.big_info_panel = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ self.big_info_panel = Gtk.Box(
+ orientation=Gtk.Orientation.HORIZONTAL, name="artist-info-panel"
+ )
self.artist_artwork = SpinnerImage(
loading=False,
@@ -179,22 +193,6 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
# Action buttons, name, comment, number of songs, etc.
artist_details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- # Action buttons (note we are packing end here, so we have to put them
- # in right-to-left).
- self.artist_action_buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
-
- view_refresh_button = IconButton("view-refresh-symbolic", "Refresh artist info")
- view_refresh_button.connect("clicked", self.on_view_refresh_click)
- self.artist_action_buttons.pack_end(view_refresh_button, False, False, 5)
-
- download_all_btn = IconButton(
- "folder-download-symbolic", "Download all songs by this artist"
- )
- download_all_btn.connect("clicked", self.on_download_all_click)
- self.artist_action_buttons.pack_end(download_all_btn, False, False, 5)
-
- artist_details_box.pack_start(self.artist_action_buttons, False, False, 5)
-
artist_details_box.pack_start(Gtk.Box(), True, False, 0)
self.artist_indicator = self.make_label(name="artist-indicator")
@@ -246,35 +244,55 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
self.big_info_panel.pack_start(artist_details_box, True, True, 10)
- artist_info_box.pack_start(self.big_info_panel, False, True, 0)
+ # Action buttons
+ action_buttons_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self.artist_action_buttons = Gtk.Box(
+ orientation=Gtk.Orientation.HORIZONTAL, spacing=10
+ )
+ download_all_btn = IconButton(
+ "folder-download-symbolic", "Download all songs by this artist"
+ )
+ download_all_btn.connect("clicked", self.on_download_all_click)
+ self.artist_action_buttons.add(download_all_btn)
+
+ view_refresh_button = IconButton("view-refresh-symbolic", "Refresh artist info")
+ view_refresh_button.connect("clicked", self.on_view_refresh_click)
+ self.artist_action_buttons.add(view_refresh_button)
+
+ action_buttons_container.pack_start(
+ self.artist_action_buttons, False, False, 10
+ )
+
+ action_buttons_container.pack_start(Gtk.Box(), True, True, 0)
+
+ expand_button_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ self.expand_collapse_button = IconButton(
+ "pan-up-symbolic", "Expand playlist details"
+ )
+ self.expand_collapse_button.connect("clicked", self.on_expand_collapse_click)
+ expand_button_container.pack_end(self.expand_collapse_button, False, False, 0)
+ action_buttons_container.add(expand_button_container)
+
+ self.big_info_panel.pack_start(action_buttons_container, False, False, 5)
+
+ self.pack_start(self.big_info_panel, False, True, 0)
+
+ self.album_list_scrolledwindow = Gtk.ScrolledWindow()
self.albums_list = AlbumsListWithSongs()
self.albums_list.connect(
"song-clicked", lambda _, *args: self.emit("song-clicked", *args),
)
- artist_info_box.pack_start(self.albums_list, True, True, 0)
-
- self.add(artist_info_box)
+ self.album_list_scrolledwindow.add(self.albums_list)
+ self.pack_start(self.album_list_scrolledwindow, True, True, 0)
def update(self, app_config: AppConfiguration):
self.artist_id = app_config.state.selected_artist_id
if app_config.state.selected_artist_id is None:
- self.artist_action_buttons.hide()
- self.artist_indicator.set_text("")
- self.artist_name.set_markup("")
- self.artist_stats.set_markup("")
-
- self.artist_bio.set_markup("")
- self.similar_artists_scrolledwindow.hide()
- self.play_shuffle_buttons.hide()
-
- self.artist_artwork.set_from_file(None)
-
- self.albums = []
- self.albums_list.update(None)
+ self.hide()
else:
self.update_order_token += 1
- self.artist_action_buttons.show()
+ self.show()
self.update_artist_view(
app_config.state.selected_artist_id,
app_config=app_config,
@@ -296,28 +314,50 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
if order_token != self.update_order_token:
return
- self.artist_indicator.set_text("ARTIST")
+ if app_config:
+ self.artist_details_expanded = app_config.state.artist_details_expanded
+
+ up_down = "up" if self.artist_details_expanded else "down"
+ self.expand_collapse_button.set_icon(f"pan-{up_down}-symbolic")
+ self.expand_collapse_button.set_tooltip_text(
+ "Collapse" if self.artist_details_expanded else "Expand"
+ )
+
self.artist_name.set_markup(util.esc(f"{artist.name}"))
- self.artist_stats.set_markup(self.format_stats(artist))
+ self.artist_name.set_tooltip_text(artist.name)
- self.artist_bio.set_markup(util.esc(artist.biography))
+ if self.artist_details_expanded:
+ self.show_all()
+ self.artist_artwork.set_image_size(300)
+ self.artist_indicator.set_text("ARTIST")
+ self.artist_stats.set_markup(self.format_stats(artist))
- if len(artist.similar_artists or []) > 0:
- self.similar_artists_label.set_markup("Similar Artists: ")
- for c in self.similar_artists_button_box.get_children():
- self.similar_artists_button_box.remove(c)
+ self.artist_bio.set_markup(util.esc(artist.biography))
- for artist in (artist.similar_artists or [])[:5]:
- self.similar_artists_button_box.add(
- Gtk.LinkButton(
- label=artist.name,
- name="similar-artist-button",
- action_name="app.go-to-artist",
- action_target=GLib.Variant("s", artist.id),
+ if len(artist.similar_artists or []) > 0:
+ self.similar_artists_label.set_markup("Similar Artists: ")
+ for c in self.similar_artists_button_box.get_children():
+ self.similar_artists_button_box.remove(c)
+
+ for artist in (artist.similar_artists or [])[:5]:
+ self.similar_artists_button_box.add(
+ Gtk.LinkButton(
+ label=artist.name,
+ name="similar-artist-button",
+ action_name="app.go-to-artist",
+ action_target=GLib.Variant("s", artist.id),
+ )
)
- )
- self.similar_artists_scrolledwindow.show_all()
+ self.similar_artists_scrolledwindow.show_all()
+ else:
+ self.similar_artists_scrolledwindow.hide()
else:
+ self.artist_name.get_style_context().add_class("collapsed")
+ self.show_all()
+ self.artist_artwork.set_image_size(70)
+ self.artist_indicator.hide()
+ self.artist_stats.hide()
+ self.artist_bio.hide()
self.similar_artists_scrolledwindow.hide()
self.play_shuffle_buttons.show_all()
@@ -353,7 +393,7 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
self.artist_id, force=True, order_token=self.update_order_token,
)
- def on_download_all_click(self, btn: Any):
+ def on_download_all_click(self, _):
AdapterManager.batch_download_songs(
self.get_artist_song_ids(),
before_download=lambda _: self.update_artist_view(
@@ -364,13 +404,13 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
),
)
- def on_play_all_clicked(self, btn: Any):
+ def on_play_all_clicked(self, _):
songs = self.get_artist_song_ids()
self.emit(
"song-clicked", 0, songs, {"force_shuffle_state": False},
)
- def on_shuffle_all_button(self, btn: Any):
+ def on_shuffle_all_button(self, _):
songs = self.get_artist_song_ids()
self.emit(
"song-clicked",
@@ -379,6 +419,13 @@ class ArtistDetailPanel(Gtk.ScrolledWindow):
{"force_shuffle_state": True},
)
+ def on_expand_collapse_click(self, _):
+ self.emit(
+ "refresh-window",
+ {"artist_details_expanded": not self.artist_details_expanded},
+ False,
+ )
+
# Helper Methods
# =========================================================================
def set_all_loading(self, loading_state: bool):
diff --git a/sublime/ui/playlists.py b/sublime/ui/playlists.py
index 3b693b9..4ed5306 100644
--- a/sublime/ui/playlists.py
+++ b/sublime/ui/playlists.py
@@ -673,7 +673,6 @@ class PlaylistDetailPanel(Gtk.Overlay):
)
def on_expand_collapse_click(self, _):
- # TODO
self.emit(
"refresh-window",
{"playlist_details_expanded": not self.playlist_details_expanded},