[charts] Add better chart views per Maloja
All checks were successful
build / test (push) Successful in 2m3s

This commit is contained in:
2026-06-09 17:04:59 -04:00
parent 15d27f6d94
commit be16d513ef
5 changed files with 184 additions and 185 deletions

View File

@ -88,7 +88,7 @@ fetching and simple saving.
*** Metadata sources *** Metadata sources
**** Scraper **** Scraper
* Backlog [0/15] :vrobbler:project:personal: * Backlog [1/17] :vrobbler:project:personal:
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles:
:PROPERTIES: :PROPERTIES:
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb :ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
@ -534,6 +534,16 @@ async with the POST data stored in the log["raw_data"] and used by the celery en
to go try to enrich the media instance. Should this enrichment fail, tag the scrobble as "enrichment-failed" to go try to enrich the media instance. Should this enrichment fail, tag the scrobble as "enrichment-failed"
log a warning and move on. log a warning and move on.
** TODO [#B] Allow browing a user's favorited media :favorites:feature:
:PROPERTIES:
:ID: 5c2cf004-d01f-4576-9bbb-974235e7408a
:END:
*** Description
We should have a global view `/favorites/` that shows the logged in users's
favorited media objects.
** TODO [#A] Allow updating all a user's scrobble visibility at once :scrobbles:sharing:feature: ** TODO [#A] Allow updating all a user's scrobble visibility at once :scrobbles:sharing:feature:
:PROPERTIES: :PROPERTIES:
:ID: 9ed2ec65-bf69-4300-965c-6a7d3ef7ea03 :ID: 9ed2ec65-bf69-4300-965c-6a7d3ef7ea03
@ -551,6 +561,17 @@ Additionally, users's should have links in their settings to see what scrobbles
are either public, shared or private. Probably this could be done with a are either public, shared or private. Probably this could be done with a
?visbility=<> filter on the /scrobbles/ page. ?visbility=<> filter on the /scrobbles/ page.
** DONE [#A] Replace columsn of Top Artists, Tracks and Series with Maloja widget :templates:charts:
:PROPERTIES:
:ID: 3946afb1-932c-46fe-a188-f4c9add1a491
:END:
*** Description
The tables are fine, but Maloja widgets are better. We should drop the top track table, add top albums
and replace top artists and top tv series with the Maloja style widgets.
* Version 49.1 [1/1] * Version 49.1 [1/1]
** DONE [#A] Fix bug with missing default visbility for scrobbles :bug:scrobbles:sharing: ** DONE [#A] Fix bug with missing default visbility for scrobbles :bug:scrobbles:sharing:
:PROPERTIES: :PROPERTIES:

View File

@ -114,108 +114,106 @@ class ChartRecordView(TemplateView):
context["current_week"] = current_week context["current_week"] = current_week
context["current_day"] = current_day context["current_day"] = current_day
if chart_type == "maloja": context["chart_keys"] = {
context["chart_keys"] = { "today": "Today",
"today": "Today", "week": "This Week",
"week": "This Week", "month": "This Month",
"month": "This Month", "year": "This Year",
"year": "This Year", "all": "All Time",
"all": "All Time", }
}
context["maloja_charts"] = { context["maloja_charts"] = {
"artist": { "artist": {
"today": list( "today": list(
self.get_charts_for_period( self.get_charts_for_period(
user, user,
"artist", "artist",
year=current_year, year=current_year,
month=current_month, month=current_month,
day=current_day, day=current_day,
) )
), ),
"week": list( "week": list(
self.get_charts_for_period( self.get_charts_for_period(
user, user,
"artist", "artist",
year=current_year, year=current_year,
week=current_week, week=current_week,
) )
), ),
"month": list( "month": list(
self.get_charts_for_period( self.get_charts_for_period(
user, user,
"artist", "artist",
year=current_year, year=current_year,
month=current_month, month=current_month,
) )
), ),
"year": list( "year": list(
self.get_charts_for_period(user, "artist", year=current_year) self.get_charts_for_period(user, "artist", year=current_year)
), ),
"all": list(self.get_charts_for_period(user, "artist")), "all": list(self.get_charts_for_period(user, "artist")),
}, },
"track": { "album": {
"today": list( "today": list(
self.get_charts_for_period( self.get_charts_for_period(
user, user,
"track", "album",
year=current_year, year=current_year,
month=current_month, month=current_month,
day=current_day, day=current_day,
) )
), ),
"week": list( "week": list(
self.get_charts_for_period( self.get_charts_for_period(
user, "track", year=current_year, week=current_week user, "album", year=current_year, week=current_week
) )
), ),
"month": list( "month": list(
self.get_charts_for_period( self.get_charts_for_period(
user, user,
"track", "album",
year=current_year, year=current_year,
month=current_month, month=current_month,
) )
), ),
"year": list( "year": list(
self.get_charts_for_period(user, "track", year=current_year) self.get_charts_for_period(user, "album", year=current_year)
), ),
"all": list(self.get_charts_for_period(user, "track")), "all": list(self.get_charts_for_period(user, "album")),
}, },
"tv_series": { "tv_series": {
"today": list( "today": list(
self.get_charts_for_period( self.get_charts_for_period(
user, user,
"tv_series", "tv_series",
year=current_year, year=current_year,
month=current_month, month=current_month,
day=current_day, day=current_day,
) )
), ),
"week": list( "week": list(
self.get_charts_for_period( self.get_charts_for_period(
user, user,
"tv_series", "tv_series",
year=current_year, year=current_year,
week=current_week, week=current_week,
) )
), ),
"month": list( "month": list(
self.get_charts_for_period( self.get_charts_for_period(
user, user,
"tv_series", "tv_series",
year=current_year, year=current_year,
month=current_month, month=current_month,
) )
), ),
"year": list( "year": list(
self.get_charts_for_period(user, "tv_series", year=current_year) self.get_charts_for_period(user, "tv_series", year=current_year)
), ),
"all": list(self.get_charts_for_period(user, "tv_series")), "all": list(self.get_charts_for_period(user, "tv_series")),
}, },
} }
return context
if not date_param: if not date_param:
context["period"] = "current" context["period"] = "current"

View File

@ -15,6 +15,7 @@
<a href="{% url 'charts:charts-home' %}" class="btn btn-sm btn-outline-secondary">Charts</a> <a href="{% url 'charts:charts-home' %}" class="btn btn-sm btn-outline-secondary">Charts</a>
</div> </div>
{% endif %} {% endif %}
{% block grid_view_button %}
<div class="btn-group me-2"> <div class="btn-group me-2">
{% if view == 'grid' %} {% if view == 'grid' %}
<button type="button" class="btn btn-sm btn-outline-secondary"><a href="?{% urlreplace view='list' %}">List View</a> <button type="button" class="btn btn-sm btn-outline-secondary"><a href="?{% urlreplace view='list' %}">List View</a>
@ -22,6 +23,7 @@
<button type="button" class="btn btn-sm btn-outline-secondary"><a href="?{% urlreplace view='grid' %}">Grid View</a> <button type="button" class="btn btn-sm btn-outline-secondary"><a href="?{% urlreplace view='grid' %}">Grid View</a>
{% endif %} {% endif %}
</div> </div>
{% endblock %}
{% block charts_button %}{% endblock %} {% block charts_button %}{% endblock %}
</div> </div>
</div> </div>

View File

@ -1,4 +1,5 @@
{% extends "base_list.html" %} {% extends "base_list.html" %}
{% load static %}
{% block title %}Charts{% if period_str %} - {{ period_str }}{% endif %}{% endblock %} {% block title %}Charts{% if period_str %} - {{ period_str }}{% endif %}{% endblock %}
@ -12,11 +13,46 @@
.nav-tabs { .nav-tabs {
cursor: pointer; cursor: pointer;
} }
.image-wrapper {
contain: content;
}
.image-wrapper :hover {
background: rgba(0,0,0,0.3);
}
.caption {
position: fixed;
top: 5px;
left: 5px;
padding: 3px;
font-size: 90%;
color: white;
background: rgba(0,0,0,0.4);
}
.caption-medium {
position: fixed;
top: 5px;
left: 5px;
padding: 3px;
font-size: 75%;
color: white;
background: rgba(0,0,0,0.4);
}
.caption-small {
position: fixed;
top: 5px;
left: 5px;
padding: 3px;
font-size: 60%;
color: white;
background: rgba(0,0,0,0.4);
}
</style> </style>
{% endblock %} {% endblock %}
{% block lists %} {% block lists %}
{% block grid_view_button %}{% endblock %}
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12"> <div class="col-12">
<a href="{% url 'charts:spotify-tracks' %}" class="btn btn-sm btn-outline-secondary">🎵 Spotify Tracks</a> <a href="{% url 'charts:spotify-tracks' %}" class="btn btn-sm btn-outline-secondary">🎵 Spotify Tracks</a>
@ -24,10 +60,6 @@
</div> </div>
</div> </div>
{% if chart_type == "maloja" %}
{% include "scrobbles/_top_charts.html" %}
{% else %}
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="btn-group mb-3" role="group"> <div class="btn-group mb-3" role="group">
@ -88,43 +120,9 @@
</div> </div>
{% endif %} {% endif %}
{% include "scrobbles/_top_charts.html" %}
<div class="row"> <div class="row">
{% if charts.artist %}
<div class="col-md-6 col-lg-4 chart-section">
<h3>🎤 Top Artists</h3>
<ul class="list-group">
{% for chart in charts.artist|slice:":20" %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="me-2"><strong>#{{chart.rank}}</strong></span>
<a href="{{chart.artist.get_absolute_url}}">{{chart.artist.name}}</a>
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'artist' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
{% if charts.album %}
<div class="col-md-6 col-lg-4 chart-section">
<h3>💿 Top Albums</h3>
<ul class="list-group">
{% for chart in charts.album|slice:":20" %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="me-2"><strong>#{{chart.rank}}</strong></span>
<a href="{{chart.album.get_absolute_url}}">{{chart.album.name}}</a>
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'album' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
{% if charts.track %} {% if charts.track %}
<div class="col-md-6 col-lg-4 chart-section"> <div class="col-md-6 col-lg-4 chart-section">
<h3>🎵 Top Tracks</h3> <h3>🎵 Top Tracks</h3>
@ -143,24 +141,6 @@
</div> </div>
{% endif %} {% endif %}
{% if charts.tv_series %}
<div class="col-md-6 col-lg-4 chart-section">
<h3>📺 Top TV Series</h3>
<ul class="list-group">
{% for chart in charts.tv_series|slice:":20" %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="me-2"><strong>#{{chart.rank}}</strong></span>
<a href="{{chart.tv_series.get_absolute_url}}">{{chart.tv_series.name}}</a>
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'tv_series' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
{% if charts.video %} {% if charts.video %}
<div class="col-md-6 col-lg-4 chart-section"> <div class="col-md-6 col-lg-4 chart-section">
<h3>🎬 Top Videos</h3> <h3>🎬 Top Videos</h3>
@ -314,6 +294,4 @@
</div> </div>
{% endif %} {% endif %}
{% endif %}
{% endblock %} {% endblock %}

View File

@ -76,44 +76,44 @@
</div> </div>
<div class="row"> <div class="row">
<h2>🎵 Top Tracks</h2> <h2>💿 Top Albums</h2>
<ul class="nav nav-tabs" id="trackTab" role="tablist"> <ul class="nav nav-tabs" id="albumTab" role="tablist">
{% for key, name in chart_keys.items %} {% for key, name in chart_keys.items %}
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link {% if forloop.counter == 2 %}active{% endif %}" <button class="nav-link {% if forloop.counter == 2 %}active{% endif %}"
id="track-{{key}}-tab" data-bs-toggle="tab" data-bs-target="#track-{{key}}" id="album-{{key}}-tab" data-bs-toggle="tab" data-bs-target="#album-{{key}}"
type="button" role="tab">{{name}}</button> type="button" role="tab">{{name}}</button>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<div class="tab-content" id="trackTabContent" class="maloja-chart"> <div class="tab-content" id="albumTabContent" class="maloja-chart">
{% for key, name in chart_keys.items %} {% for key, name in chart_keys.items %}
{% with maloja_charts.track|get_item:key as tracks %} {% with maloja_charts.album|get_item:key as albums %}
<div class="tab-pane fade {% if forloop.counter == 2 %}show active{% endif %}" id="track-{{key}}" role="tabpanel"> <div class="tab-pane fade {% if forloop.counter == 2 %}show active{% endif %}" id="album-{{key}}" role="tabpanel">
{% if tracks.0 %} {% if albums.0 %}
<div style="display:block"> <div style="display:block">
<div style="float:left;"> <div style="float:left;">
<div class="image-wrapper" style="display:flex; flex-wrap: wrap; margin:0"> <div class="image-wrapper" style="display:flex; flex-wrap: wrap; margin:0">
<div class="caption">#1 {{tracks.0.track.title}}</div> <div class="caption">#1 {{albums.0.album.title}}</div>
{% if tracks.0.track.album.cover_image %} {% if albums.0.album.cover_image %}
<a href="{{tracks.0.track.get_absolute_url}}"><img src="{{tracks.0.track.album.cover_image_medium.url}}" width="300px"></a> <a href="{{albums.0.album.get_absolute_url}}"><img src="{{albums.0.album.cover_image_medium.url}}" width="300px"></a>
{% else %} {% else %}
<a href="{{tracks.0.track.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="300px"></a> <a href="{{albums.0.album.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="300px"></a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div style="float:left; width:300px;"> <div style="float:left; width:300px;">
<div style="display:flex; flex-wrap: wrap;"> <div style="display:flex; flex-wrap: wrap;">
{% for i in "2345" %} {% for i in "2345" %}
{% with tracks|get_item:forloop.counter as track %} {% with albums|get_item:forloop.counter as album %}
{% if track %} {% if album %}
<div class="image-wrapper" style="width:50%"> <div class="image-wrapper" style="width:50%">
<div class="caption-medium">#{{forloop.counter|add:1}} {{track.track.title}}</div> <div class="caption-medium">#{{forloop.counter|add:1}} {{album.album.title}}</div>
{% if track.track.album.cover_image %} {% if album.album.cover_image %}
<a href="{{track.track.get_absolute_url}}"><img src="{{track.track.album.cover_image_medium.url}}" width="150px"></a> <a href="{{album.album.get_absolute_url}}"><img src="{{album.album.cover_image_medium.url}}" width="150px"></a>
{% else %} {% else %}
<a href="{{track.track.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="150px"></a> <a href="{{album.album.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="150px"></a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@ -124,14 +124,14 @@
<div style="float:left; width:300px;"> <div style="float:left; width:300px;">
<div style="display:flex; flex-wrap: wrap;"> <div style="display:flex; flex-wrap: wrap;">
{% for i in "67891011121314" %} {% for i in "67891011121314" %}
{% with tracks|get_item:forloop.counter|add:5 as track %} {% with albums|get_item:forloop.counter|add:5 as album %}
{% if track %} {% if album %}
<div class="image-wrapper" style="width:33%"> <div class="image-wrapper" style="width:33%">
<div class="caption-small">#{{forloop.counter|add:6}} {{track.track.title}}</div> <div class="caption-small">#{{forloop.counter|add:6}} {{album.album.title}}</div>
{% if track.track.album.cover_image %} {% if album.album.cover_image %}
<a href="{{track.track.get_absolute_url}}"><img src="{{track.track.album.cover_image_medium.url}}" width="100px"></a> <a href="{{album.album.get_absolute_url}}"><img src="{{album.album.cover_image_medium.url}}" width="100px"></a>
{% else %} {% else %}
<a href="{{track.track.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a> <a href="{{album.album.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}