[templates] Clean up widgets
This commit is contained in:
@ -1,30 +1,25 @@
|
||||
from django.urls import path
|
||||
from scrobbles import views
|
||||
from tasks.webhooks import todoist_webhook, emacs_webhook
|
||||
from tasks.webhooks import emacs_webhook, todoist_webhook
|
||||
|
||||
app_name = "scrobbles"
|
||||
|
||||
urlpatterns = [
|
||||
path("status/", views.ScrobbleStatusView.as_view(), name="status"),
|
||||
path(
|
||||
"widget/top-artists/<int:user_id>/",
|
||||
views.EmbeddableTopArtistWidget.as_view(),
|
||||
"widget/top-artists/<str:period>/<int:user_id>/",
|
||||
views.EmbeddableTopArtistsWidget.as_view(),
|
||||
name="embeddable-top-artists",
|
||||
),
|
||||
path(
|
||||
"widget/top-board-games/month/<int:user_id>/",
|
||||
views.EmbeddableTopBoardGamesMonthWidget.as_view(),
|
||||
name="embeddable-top-board-games-month",
|
||||
"widget/top-board-games/<str:period>/<int:user_id>/",
|
||||
views.EmbeddableTopBoardGamesWidget.as_view(),
|
||||
name="embeddable-top-board-games",
|
||||
),
|
||||
path(
|
||||
"widget/top-board-games/week/<int:user_id>/",
|
||||
views.EmbeddableTopBoardGamesWeekWidget.as_view(),
|
||||
name="embeddable-top-board-games-week",
|
||||
),
|
||||
path(
|
||||
"widget/top-board-games/year/<int:user_id>/",
|
||||
views.EmbeddableTopBoardGamesYearWidget.as_view(),
|
||||
name="embeddable-top-board-games-year",
|
||||
"widget/top-books/<str:period>/<int:user_id>/",
|
||||
views.EmbeddableTopBooksWidget.as_view(),
|
||||
name="embeddable-top-books",
|
||||
),
|
||||
path(
|
||||
"manual/lookup/",
|
||||
|
||||
@ -8,12 +8,17 @@ import pytz
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.apps import apps
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||
from django.db.models import Count, Max, Q
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import FileResponse, Http404, HttpResponseRedirect, JsonResponse
|
||||
from django.http import (
|
||||
FileResponse,
|
||||
Http404,
|
||||
HttpResponseRedirect,
|
||||
JsonResponse,
|
||||
)
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
@ -24,13 +29,15 @@ from django.views.generic.list import ListView
|
||||
from moods.models import Mood
|
||||
from music.aggregators import (
|
||||
artist_scrobble_count,
|
||||
week_of_scrobbles,
|
||||
scrobble_counts,
|
||||
live_charts,
|
||||
live_tv_charts,
|
||||
live_youtube_channel_charts,
|
||||
scrobble_counts,
|
||||
week_of_scrobbles,
|
||||
)
|
||||
from pendulum.parsing.exceptions import ParserError
|
||||
from profiles.models import UserProfile
|
||||
from profiles.utils import now_user_timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import (
|
||||
api_view,
|
||||
@ -67,8 +74,6 @@ from scrobbles.utils import (
|
||||
get_long_plays_completed,
|
||||
get_long_plays_in_progress,
|
||||
)
|
||||
from profiles.models import UserProfile
|
||||
from profiles.utils import now_user_timezone
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@ -1019,170 +1024,156 @@ class ScrobbleDetailView(DetailView):
|
||||
return context
|
||||
|
||||
|
||||
class EmbeddableTopArtistWidget(TemplateView):
|
||||
template_name = "scrobbles/embeddable_top_artist.html"
|
||||
class BaseEmbeddableWidget(TemplateView):
|
||||
template_name = "scrobbles/embeddable_top_media.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
media_type = "Media"
|
||||
count_label = "plays"
|
||||
no_data_message = "No scrobbles"
|
||||
model = None
|
||||
scrobble_filter = {}
|
||||
|
||||
def get_user_and_profile(self):
|
||||
user_id = self.kwargs.get("user_id")
|
||||
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
except User.DoesNotExist:
|
||||
context["error"] = "User not found"
|
||||
return context
|
||||
|
||||
user = User.objects.get(id=user_id)
|
||||
profile = user.profile
|
||||
if not profile.enable_public_widgets:
|
||||
raise Http404("Public widgets are not enabled for this user")
|
||||
return user, profile
|
||||
|
||||
def get_date_range(self, user):
|
||||
now = timezone.now()
|
||||
if user.is_authenticated:
|
||||
now = now_user_timezone(user.profile)
|
||||
now = now.date()
|
||||
|
||||
period = self.kwargs.get("period", "week")
|
||||
if period == "month":
|
||||
start = now.replace(day=1)
|
||||
label = now.strftime("%B %Y")
|
||||
elif period == "year":
|
||||
start = now.replace(month=1, day=1)
|
||||
label = now.strftime("%Y")
|
||||
else:
|
||||
start = now - timedelta(days=now.isoweekday() % 7)
|
||||
label = "This Week"
|
||||
|
||||
return start, label
|
||||
|
||||
def get_items(self, user, start_date, model=None):
|
||||
from django.db.models import Count
|
||||
|
||||
model = model or self.model
|
||||
queryset = (
|
||||
model.objects.filter(
|
||||
scrobble__user=user,
|
||||
scrobble__timestamp__gte=start_date,
|
||||
**self.scrobble_filter,
|
||||
)
|
||||
.annotate(count=Count("scrobble", distinct=True))
|
||||
.order_by("-count")[:10]
|
||||
)
|
||||
|
||||
items = list(queryset)
|
||||
for item in items:
|
||||
if not hasattr(item, "name") and hasattr(item, "title"):
|
||||
item.name = item.title
|
||||
return items
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
user, profile = self.get_user_and_profile()
|
||||
|
||||
context["user"] = user
|
||||
context["custom_css"] = profile.widget_custom_css
|
||||
context["media_type"] = self.media_type
|
||||
context["count_label"] = self.count_label
|
||||
|
||||
artist = {
|
||||
"user": user,
|
||||
"media_type": "Artist",
|
||||
"limit": 10,
|
||||
}
|
||||
context["top_artists"] = list(live_charts(**artist, chart_period="last7"))
|
||||
start_date, period_label = self.get_date_range(user)
|
||||
context["period_label"] = period_label
|
||||
|
||||
items = self.get_items(user, start_date)
|
||||
context["items"] = items
|
||||
context["no_data_message"] = self.no_data_message
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class EmbeddableTopBoardGamesMonthWidget(TemplateView):
|
||||
template_name = "scrobbles/embeddable_top_board_games_month.html"
|
||||
class EmbeddableTopArtistsWidget(TemplateView):
|
||||
template_name = "scrobbles/embeddable_top_media.html"
|
||||
media_type = "Artists"
|
||||
count_label = "scrobbles"
|
||||
no_data_message = "No scrobbles"
|
||||
|
||||
def get_user_and_profile(self):
|
||||
user_id = self.kwargs.get("user_id")
|
||||
user = User.objects.get(id=user_id)
|
||||
profile = user.profile
|
||||
if not profile.enable_public_widgets:
|
||||
raise Http404("Public widgets are not enabled for this user")
|
||||
return user, profile
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
from django.db.models import Count
|
||||
from django.utils import timezone
|
||||
context = super().get_context_data(**kwargs)
|
||||
user, profile = self.get_user_and_profile()
|
||||
|
||||
context["user"] = user
|
||||
context["custom_css"] = profile.widget_custom_css
|
||||
context["media_type"] = self.media_type
|
||||
context["count_label"] = self.count_label
|
||||
context["no_data_message"] = self.no_data_message
|
||||
|
||||
period = self.kwargs.get("period", "week")
|
||||
period_map = {"week": "last7", "month": "last30", "year": "year"}
|
||||
chart_period = period_map.get(period, "last7")
|
||||
|
||||
artist = {"user": user, "media_type": "Artist", "limit": 10}
|
||||
items = list(live_charts(**artist, chart_period=chart_period))
|
||||
|
||||
for item in items:
|
||||
item.count = item.num_scrobbles
|
||||
|
||||
context["items"] = items
|
||||
|
||||
from datetime import timedelta
|
||||
from boardgames.models import BoardGame
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
user_id = self.kwargs.get("user_id")
|
||||
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
except User.DoesNotExist:
|
||||
context["error"] = "User not found"
|
||||
return context
|
||||
|
||||
profile = user.profile
|
||||
if not profile.enable_public_widgets:
|
||||
raise Http404("Public widgets are not enabled for this user")
|
||||
|
||||
context["user"] = user
|
||||
context["custom_css"] = profile.widget_custom_css
|
||||
|
||||
now = timezone.now()
|
||||
if user.is_authenticated:
|
||||
now = now_user_timezone(user.profile)
|
||||
now = now.date()
|
||||
start_day_of_month = now.replace(day=1)
|
||||
|
||||
top_games = (
|
||||
BoardGame.objects.filter(
|
||||
scrobble__user=user,
|
||||
scrobble__timestamp__gte=start_day_of_month,
|
||||
scrobble__played_to_completion=True,
|
||||
)
|
||||
.annotate(num_plays=Count("scrobble", distinct=True))
|
||||
.order_by("-num_plays")[:10]
|
||||
)
|
||||
|
||||
context["top_board_games"] = list(top_games)
|
||||
context["month"] = now.strftime("%B %Y")
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class EmbeddableTopBoardGamesWeekWidget(TemplateView):
|
||||
template_name = "scrobbles/embeddable_top_board_games_week.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
from django.db.models import Count
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from boardgames.models import BoardGame
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
user_id = self.kwargs.get("user_id")
|
||||
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
except User.DoesNotExist:
|
||||
context["error"] = "User not found"
|
||||
return context
|
||||
|
||||
profile = user.profile
|
||||
if not profile.enable_public_widgets:
|
||||
raise Http404("Public widgets are not enabled for this user")
|
||||
|
||||
context["user"] = user
|
||||
context["custom_css"] = profile.widget_custom_css
|
||||
|
||||
now = timezone.now()
|
||||
if user.is_authenticated:
|
||||
now = now_user_timezone(user.profile)
|
||||
now = now.date()
|
||||
start_day_of_week = now - timedelta(days=now.isoweekday() % 7)
|
||||
|
||||
top_games = (
|
||||
BoardGame.objects.filter(
|
||||
scrobble__user=user,
|
||||
scrobble__timestamp__gte=start_day_of_week,
|
||||
scrobble__played_to_completion=True,
|
||||
)
|
||||
.annotate(num_plays=Count("scrobble", distinct=True))
|
||||
.order_by("-num_plays")[:10]
|
||||
)
|
||||
|
||||
context["top_board_games"] = list(top_games)
|
||||
if period == "month":
|
||||
context["period_label"] = now.strftime("%B %Y")
|
||||
elif period == "year":
|
||||
context["period_label"] = now.strftime("%Y")
|
||||
else:
|
||||
start = now - timedelta(days=now.isoweekday() % 7)
|
||||
context["period_label"] = "This Week"
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class EmbeddableTopBoardGamesYearWidget(TemplateView):
|
||||
template_name = "scrobbles/embeddable_top_board_games_year.html"
|
||||
class EmbeddableTopBoardGamesWidget(BaseEmbeddableWidget):
|
||||
media_type = "Board Games"
|
||||
count_label = "plays"
|
||||
no_data_message = "No board games played"
|
||||
scrobble_filter = {"scrobble__played_to_completion": True}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
from django.db.models import Count
|
||||
from django.utils import timezone
|
||||
def get_items(self, user, start_date):
|
||||
from boardgames.models import BoardGame
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
user_id = self.kwargs.get("user_id")
|
||||
return super().get_items(user, start_date, BoardGame)
|
||||
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
except User.DoesNotExist:
|
||||
context["error"] = "User not found"
|
||||
return context
|
||||
|
||||
profile = user.profile
|
||||
if not profile.enable_public_widgets:
|
||||
raise Http404("Public widgets are not enabled for this user")
|
||||
class EmbeddableTopBooksWidget(BaseEmbeddableWidget):
|
||||
media_type = "Books"
|
||||
count_label = "reads"
|
||||
no_data_message = "No books read"
|
||||
scrobble_filter = {"scrobble__long_play_complete": True}
|
||||
|
||||
context["user"] = user
|
||||
context["custom_css"] = profile.widget_custom_css
|
||||
def get_items(self, user, start_date):
|
||||
from books.models import Book
|
||||
|
||||
now = timezone.now()
|
||||
if user.is_authenticated:
|
||||
now = now_user_timezone(user.profile)
|
||||
now = now.date()
|
||||
start_day_of_year = now.replace(month=1, day=1)
|
||||
|
||||
top_games = (
|
||||
BoardGame.objects.filter(
|
||||
scrobble__user=user,
|
||||
scrobble__timestamp__gte=start_day_of_year,
|
||||
scrobble__played_to_completion=True,
|
||||
)
|
||||
.annotate(num_plays=Count("scrobble", distinct=True))
|
||||
.order_by("-num_plays")[:10]
|
||||
)
|
||||
|
||||
context["top_board_games"] = list(top_games)
|
||||
context["year"] = now.strftime("%Y")
|
||||
|
||||
return context
|
||||
return super().get_items(user, start_date, Book)
|
||||
|
||||
@ -111,10 +111,15 @@
|
||||
<div class="widget-links">
|
||||
<strong>Available Widgets:</strong>
|
||||
<ul>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-artists' user_id %}">Top Artists</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-board-games-week' user_id %}">Top Board Games (Week)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-board-games-month' user_id %}">Top Board Games (Month)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-board-games-year' user_id %}">Top Board Games (Year)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-artists' 'week' user_id %}">Top Artists (Week)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-artists' 'month' user_id %}">Top Artists (Month)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-artists' 'year' user_id %}">Top Artists (Year)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-board-games' 'week' user_id %}">Top Board Games (Week)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-board-games' 'month' user_id %}">Top Board Games (Month)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-board-games' 'year' user_id %}">Top Board Games (Year)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-books' 'week' user_id %}">Top Books (Week)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-books' 'month' user_id %}">Top Books (Month)</a></li>
|
||||
<li><a href="{% url 'scrobbles:embeddable-top-books' 'year' user_id %}">Top Books (Year)</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
{% load static %}
|
||||
<style>
|
||||
.vrobbler-widget {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
max-width: 400px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
.vrobbler-widget h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
.vrobbler-artist-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.vrobbler-artist-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.vrobbler-artist-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.vrobbler-rank {
|
||||
width: 24px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
.vrobbler-artist-info {
|
||||
flex: 1;
|
||||
}
|
||||
.vrobbler-artist-name {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.vrobbler-scrobble-count {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.vrobbler-artist-thumb {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.vrobbler-error {
|
||||
color: #d32f2f;
|
||||
}
|
||||
.vrobbler-no-data {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
{% if custom_css %}
|
||||
{{ custom_css }}
|
||||
{% endif %}
|
||||
</style>
|
||||
|
||||
<div class="vrobbler-widget">
|
||||
<h3>Last 7 Days Top Artists{% if user %} - {{ user.username }}{% endif %}</h3>
|
||||
|
||||
{% if error %}
|
||||
<p class="vrobbler-error">{{ error }}</p>
|
||||
{% elif top_artists %}
|
||||
<ul class="vrobbler-artist-list">
|
||||
{% for artist in top_artists %}
|
||||
<li class="vrobbler-artist-item">
|
||||
<span class="vrobbler-rank">{{ forloop.counter }}</span>
|
||||
{% if artist.thumbnail %}
|
||||
<img class="vrobbler-artist-thumb" src="{{ artist.thumbnail_small.url }}" alt="{{ artist.name }}">
|
||||
{% else %}
|
||||
<img class="vrobbler-artist-thumb" src="{% static 'images/not-found.jpg' %}" alt="{{ artist.name }}">
|
||||
{% endif %}
|
||||
<div class="vrobbler-artist-info">
|
||||
<div class="vrobbler-artist-name">{{ artist.name }}</div>
|
||||
<div class="vrobbler-scrobble-count">{{ artist.num_scrobbles }} scrobbles</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="vrobbler-no-data">No scrobbles in the last 7 days</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -1,90 +0,0 @@
|
||||
{% load static %}
|
||||
<style>
|
||||
.vrobbler-widget {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
max-width: 400px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
.vrobbler-widget h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
.vrobbler-board-game-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.vrobbler-board-game-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.vrobbler-board-game-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.vrobbler-rank {
|
||||
width: 24px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
.vrobbler-board-game-info {
|
||||
flex: 1;
|
||||
}
|
||||
.vrobbler-board-game-title {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.vrobbler-play-count {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.vrobbler-board-game-thumb {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.vrobbler-error {
|
||||
color: #d32f2f;
|
||||
}
|
||||
.vrobbler-no-data {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
{% if custom_css %}
|
||||
{{ custom_css }}
|
||||
{% endif %}
|
||||
</style>
|
||||
|
||||
<div class="vrobbler-widget">
|
||||
<h3>Top Board Games - This Week{% if user %} - {{ user.username }}{% endif %}</h3>
|
||||
|
||||
{% if error %}
|
||||
<p class="vrobbler-error">{{ error }}</p>
|
||||
{% elif top_board_games %}
|
||||
<ul class="vrobbler-board-game-list">
|
||||
{% for game in top_board_games %}
|
||||
<li class="vrobbler-board-game-item">
|
||||
<span class="vrobbler-rank">{{ forloop.counter }}</span>
|
||||
{% if game.cover %}
|
||||
<img class="vrobbler-board-game-thumb" src="{{ game.cover_small.url }}" alt="{{ game.title }}">
|
||||
{% else %}
|
||||
<img class="vrobbler-board-game-thumb" src="{% static 'images/not-found.jpg' %}" alt="{{ game.title }}">
|
||||
{% endif %}
|
||||
<div class="vrobbler-board-game-info">
|
||||
<div class="vrobbler-board-game-title">{{ game.title }}</div>
|
||||
<div class="vrobbler-play-count">{{ game.num_plays }} plays</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="vrobbler-no-data">No board games played this week</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -1,90 +0,0 @@
|
||||
{% load static %}
|
||||
<style>
|
||||
.vrobbler-widget {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
max-width: 400px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
.vrobbler-widget h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
.vrobbler-board-game-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.vrobbler-board-game-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.vrobbler-board-game-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.vrobbler-rank {
|
||||
width: 24px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
.vrobbler-board-game-info {
|
||||
flex: 1;
|
||||
}
|
||||
.vrobbler-board-game-title {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.vrobbler-play-count {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.vrobbler-board-game-thumb {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.vrobbler-error {
|
||||
color: #d32f2f;
|
||||
}
|
||||
.vrobbler-no-data {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
{% if custom_css %}
|
||||
{{ custom_css }}
|
||||
{% endif %}
|
||||
</style>
|
||||
|
||||
<div class="vrobbler-widget">
|
||||
<h3>Top Board Games - {{ year }}{% if user %} - {{ user.username }}{% endif %}</h3>
|
||||
|
||||
{% if error %}
|
||||
<p class="vrobbler-error">{{ error }}</p>
|
||||
{% elif top_board_games %}
|
||||
<ul class="vrobbler-board-game-list">
|
||||
{% for game in top_board_games %}
|
||||
<li class="vrobbler-board-game-item">
|
||||
<span class="vrobbler-rank">{{ forloop.counter }}</span>
|
||||
{% if game.cover %}
|
||||
<img class="vrobbler-board-game-thumb" src="{{ game.cover_small.url }}" alt="{{ game.title }}">
|
||||
{% else %}
|
||||
<img class="vrobbler-board-game-thumb" src="{% static 'images/not-found.jpg' %}" alt="{{ game.title }}">
|
||||
{% endif %}
|
||||
<div class="vrobbler-board-game-info">
|
||||
<div class="vrobbler-board-game-title">{{ game.title }}</div>
|
||||
<div class="vrobbler-play-count">{{ game.num_plays }} plays</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="vrobbler-no-data">No board games played this year</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -13,18 +13,18 @@
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
.vrobbler-board-game-list {
|
||||
.vrobbler-media-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.vrobbler-board-game-item {
|
||||
.vrobbler-media-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.vrobbler-board-game-item:last-child {
|
||||
.vrobbler-media-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.vrobbler-rank {
|
||||
@ -32,18 +32,18 @@
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
.vrobbler-board-game-info {
|
||||
.vrobbler-media-info {
|
||||
flex: 1;
|
||||
}
|
||||
.vrobbler-board-game-title {
|
||||
.vrobbler-media-title {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.vrobbler-play-count {
|
||||
.vrobbler-media-count {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.vrobbler-board-game-thumb {
|
||||
.vrobbler-media-thumb {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
@ -63,28 +63,30 @@
|
||||
</style>
|
||||
|
||||
<div class="vrobbler-widget">
|
||||
<h3>Top Board Games - {{ month }}{% if user %} - {{ user.username }}{% endif %}</h3>
|
||||
<h3>Top {{ media_type }} - {{ period_label }}{% if user %} - {{ user.username }}{% endif %}</h3>
|
||||
|
||||
{% if error %}
|
||||
<p class="vrobbler-error">{{ error }}</p>
|
||||
{% elif top_board_games %}
|
||||
<ul class="vrobbler-board-game-list">
|
||||
{% for game in top_board_games %}
|
||||
<li class="vrobbler-board-game-item">
|
||||
{% elif items %}
|
||||
<ul class="vrobbler-media-list">
|
||||
{% for item in items %}
|
||||
<li class="vrobbler-media-item">
|
||||
<span class="vrobbler-rank">{{ forloop.counter }}</span>
|
||||
{% if game.cover %}
|
||||
<img class="vrobbler-board-game-thumb" src="{{ game.cover_small.url }}" alt="{{ game.title }}">
|
||||
{% if item.cover %}
|
||||
<img class="vrobbler-media-thumb" src="{{ item.cover_small.url }}" alt="{{ item.name }}">
|
||||
{% elif item.thumbnail %}
|
||||
<img class="vrobbler-media-thumb" src="{{ item.thumbnail_small.url }}" alt="{{ item.name }}">
|
||||
{% else %}
|
||||
<img class="vrobbler-board-game-thumb" src="{% static 'images/not-found.jpg' %}" alt="{{ game.title }}">
|
||||
<img class="vrobbler-media-thumb" src="{% static 'images/not-found.jpg' %}" alt="{{ item.name }}">
|
||||
{% endif %}
|
||||
<div class="vrobbler-board-game-info">
|
||||
<div class="vrobbler-board-game-title">{{ game.title }}</div>
|
||||
<div class="vrobbler-play-count">{{ game.num_plays }} plays</div>
|
||||
<div class="vrobbler-media-info">
|
||||
<div class="vrobbler-media-title">{{ item.name }}</div>
|
||||
<div class="vrobbler-media-count">{{ item.count }} {{ count_label }}</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="vrobbler-no-data">No board games played this month</p>
|
||||
<p class="vrobbler-no-data">{{ no_data_message }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
Reference in New Issue
Block a user