Merge pull request #1130 from busliggabor/feature/progress_line

[Feature] Progress line on media items
This commit is contained in:
Xila Cai
2026-02-03 22:15:44 +01:00
committed by GitHub
12 changed files with 186 additions and 16 deletions

View File

@@ -5,16 +5,61 @@ from app.models import MediaTypes, Sources, Status
# --- Color Constants ---
COLORS = {
"emerald": {"text": "text-emerald-400", "hex": "#10b981"},
"purple": {"text": "text-purple-400", "hex": "#a855f7"},
"indigo": {"text": "text-indigo-400", "hex": "#6366f1"},
"orange": {"text": "text-orange-400", "hex": "#f97316"},
"blue": {"text": "text-blue-400", "hex": "#3b82f6"},
"red": {"text": "text-red-400", "hex": "#ef4444"},
"yellow": {"text": "text-yellow-400", "hex": "#eab308"},
"fuchsia": {"text": "text-fuchsia-400", "hex": "#d946ef"},
"cyan": {"text": "text-cyan-400", "hex": "#06b6d4"},
"lime": {"text": "text-lime-400", "hex": "#84cc16"},
"emerald": {
"text": "text-emerald-400",
"background": "bg-emerald-400",
"hex": "#10b981",
},
"purple": {
"text": "text-purple-400",
"background": "bg-purple-400",
"hex": "#a855f7",
},
"indigo": {
"text": "text-indigo-400",
"background": "bg-indigo-400",
"hex": "#6366f1",
},
"orange": {
"text": "text-orange-400",
"background": "bg-orange-400",
"hex": "#f97316",
},
"blue": {
"text": "text-blue-400",
"background": "bg-blue-400",
"hex": "#3b82f6",
},
"red": {
"text": "text-red-400",
"background": "bg-red-400",
"hex": "#ef4444",
},
"yellow": {
"text": "text-yellow-400",
"background": "bg-yellow-400",
"hex": "#eab308",
},
"fuchsia": {
"text": "text-fuchsia-400",
"background": "bg-fuchsia-400",
"hex": "#d946ef",
},
"cyan": {
"text": "text-cyan-400",
"background": "bg-cyan-400",
"hex": "#06b6d4",
},
"lime": {
"text": "text-lime-400",
"background": "bg-lime-400",
"hex": "#84cc16",
},
"sky": {
"text": "text-sky-400",
"background": "bg-sky-400",
"hex": "#87ceeb",
},
}
# --- Central Configuration Dictionary ---
@@ -180,22 +225,27 @@ STATUS_CONFIG = {
Status.COMPLETED.value: {
"text_color": COLORS["emerald"]["text"],
"stats_color": COLORS["emerald"]["hex"],
"background_color": COLORS["emerald"]["background"],
},
Status.IN_PROGRESS.value: {
"text_color": COLORS["indigo"]["text"],
"stats_color": COLORS["indigo"]["hex"],
"background_color": COLORS["indigo"]["background"],
},
Status.PAUSED.value: {
"text_color": COLORS["orange"]["text"],
"stats_color": COLORS["orange"]["hex"],
"background_color": COLORS["orange"]["background"],
},
Status.PLANNING.value: {
"text_color": COLORS["blue"]["text"],
"stats_color": COLORS["blue"]["hex"],
"text_color": COLORS["sky"]["text"],
"stats_color": COLORS["sky"]["hex"],
"background_color": COLORS["sky"]["background"],
},
Status.DROPPED.value: {
"text_color": COLORS["red"]["text"],
"stats_color": COLORS["red"]["hex"],
"background_color": COLORS["red"]["background"],
},
}
@@ -305,3 +355,8 @@ def get_status_text_color(status):
def get_status_stats_color(status):
"""Get the stats color for a status."""
return get_status_property(status, "stats_color")
def get_status_background_color(status):
"""Get the background color for a status."""
return get_status_property(status, "background_color")

View File

@@ -228,6 +228,12 @@ def status_color(status):
return config.get_status_text_color(status)
@register.filter
def status_background_color(status):
"""Return the background color associated with the status."""
return config.get_status_background_color(status)
@register.filter
def natural_day(datetime, user):
"""Format date with natural language (Today, Tomorrow, etc.)."""

View File

@@ -80,6 +80,7 @@ def progress_edit(request, media_type, instance_id):
context = {
"media": media,
"user": request.user,
}
return render(
request,

View File

@@ -44,6 +44,7 @@
--color-fuchsia-400: oklch(74% 0.238 322.16);
--color-fuchsia-600: oklch(59.1% 0.293 322.896);
--color-fuchsia-700: oklch(51.8% 0.253 323.949);
--color-sky-400: oklch(0.746 0.16 232.661);
--color-slate-200: oklch(92.9% 0.013 255.508);
--color-slate-400: oklch(70.4% 0.04 256.788);
--color-gray-100: oklch(96.7% 0.003 264.542);
@@ -1239,6 +1240,8 @@
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 80%, transparent);
}
}.bg-blue-400 {
background-color: var(--color-blue-400);
}
.bg-blue-400\/10 {
background-color: color-mix(in srgb, oklch(70.7% 0.165 254.624) 10%, transparent);
@@ -1246,6 +1249,12 @@
background-color: color-mix(in oklab, var(--color-blue-400) 10%, transparent);
}
}
.bg-cyan-400 {
background-color: var(--color-cyan-400);
}
.bg-emerald-400 {
background-color: var(--color-emerald-400);
}
.bg-emerald-400\/10 {
background-color: color-mix(in srgb, oklch(76.5% 0.177 163.223) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -1261,6 +1270,9 @@
background-color: color-mix(in oklab, var(--color-emerald-600) 20%, transparent);
}
}
.bg-fuchsia-400 {
background-color: var(--color-fuchsia-400);
}
.bg-fuchsia-600 {
background-color: var(--color-fuchsia-600);
}
@@ -1315,6 +1327,18 @@
.bg-indigo-700 {
background-color: var(--color-indigo-700);
}
.bg-lime-400 {
background-color: var(--color-lime-400);
}
.bg-orange-400 {
background-color: var(--color-orange-400);
}
.bg-purple-400 {
background-color: var(--color-purple-400);
}
.bg-red-400 {
background-color: var(--color-red-400);
}
.bg-red-400\/10 {
background-color: color-mix(in srgb, oklch(70.4% 0.191 22.216) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -1351,12 +1375,18 @@
background-color: color-mix(in oklab, var(--color-red-900) 60%, transparent);
}
}
.bg-sky-400 {
background-color: var(--color-sky-400);
}
.bg-violet-600 {
background-color: var(--color-violet-600);
}
.bg-white {
background-color: var(--color-white);
}
.bg-yellow-400 {
background-color: var(--color-yellow-400);
}
.bg-yellow-400\/10 {
background-color: color-mix(in srgb, oklch(85.2% 0.199 91.936) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -1507,6 +1537,9 @@
.pl-\[36\.68px\] {
padding-left: 36.68px;
}
.pb-3 {
padding-bottom: calc(var(--spacing) * 3);
}
.text-center {
text-align: center;
}
@@ -1658,6 +1691,9 @@
.text-red-500 {
color: var(--color-red-500);
}
.text-sky-400 {
color: var(--color-sky-400);
}
.text-slate-200 {
color: var(--color-slate-200);
}

View File

@@ -60,12 +60,14 @@
</div>
</div>
<div class="p-3 space-y-3">
<a href="{{ media.item|media_url }}"
<div>
<div class="p-3 space-y-3">
<a href="{{ media.item|media_url }}"
class="text-sm font-semibold text-white hover:text-indigo-400 transition duration-300 line-clamp-1 text-center"
title="{{ media }}">{{ media }}</a>
</div>
<div id="progress-{{ media.item.media_type }}-{{ media.id }}">
{% include "app/components/progress_changer.html" with media=media csrf_token=csrf_token MediaTypes=MediaTypes only %}
{% include "app/components/progress_changer.html" with media=media user=user csrf_token=csrf_token MediaTypes=MediaTypes only %}
</div>
</div>

View File

@@ -153,4 +153,5 @@
<div id="{% component_id 'history' item %}"></div>
</div>
</div>
{% include "app/components/progress_bar.html" with media=media user=user only %}
</div>

View File

@@ -0,0 +1,17 @@
{% load app_tags %}
{% if media.status and user.progress_bar %}
<div>
{% if media.status == 'In progress' %}
{% if media.max_progress and media.max_progress > 0%}
{% widthratio media.progress media.max_progress 100 as progress_width %}
<div class="{{ media.status|status_background_color }}" style="height: 5px; width: {{ progress_width }}%;"></div>
{% else %}
<div class="{{ media.status|status_background_color }}" style="height: 5px; width: 100%;"></div>
{% endif %}
{% else %}
<div class="{{ media.status|status_background_color }}" style="height: 5px; width: 100%;"></div>
{% endif %}
</div>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% load app_tags %}
<div class="flex items-center justify-center space-x-4">
<div class="flex items-center justify-center space-x-4 pb-3">
<button class="p-1 bg-gray-700 hover:bg-gray-600 rounded-md transition-colors cursor-pointer disabled:opacity-50 disabled:hover:bg-gray-700 disabled:cursor-not-allowed"
{% if media.progress == 0 %}disabled{% endif %}
hx-post="{% url 'progress_edit' media.item.media_type media.id %}"
@@ -33,3 +33,4 @@
{% include "app/icons/plus.svg" with classes="w-3 h-3" %}
</button>
</div>
{% include "app/components/progress_bar.html" with media=media user=user %}

View File

@@ -62,6 +62,29 @@
</div>
</div>
{# Progress bar #}
<div class="mb-5">
<div class="flex items-center justify-between p-3 bg-[#39404b] rounded-md">
<div class="flex-1">
<div class="flex items-center text-gray-200 mb-1">
{% include "app/icons/minus.svg" with classes="w-5 h-5 mr-2" %}
<span class="text-sm font-medium">Show progress bar on media items</span>
</div>
<p class="text-xs text-gray-400 ml-7">
Display a progress bar on media items. Items which don't have max progress will be using a dotted line.
</p>
</div>
<label class="relative inline-flex items-center cursor-pointer ml-4">
<input class="sr-only peer"
type="checkbox"
name="progress_bar"
{% if user.progress_bar %}checked{% endif %}>
<div class="w-9 h-5 bg-gray-600 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-px after:left-0.5 after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4.5 after:w-4.5 after:transition-all peer-checked:bg-indigo-600">
</div>
</label>
</div>
</div>
{# Date Format #}
<div class="mb-5">
<div class="flex items-center justify-between p-3 bg-[#39404b] rounded-md">

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.9 on 2026-01-26 11:02
from django.db import migrations, models
class Migration(migrations.Migration):
"""Migration for user progress bar setting."""
dependencies = [
("users", "0043_add_boardgame_preferences"),
]
operations = [
migrations.AddField(
model_name="user",
name="progress_bar",
field=models.BooleanField(default=False, help_text="Show progress bar"),
),
]

View File

@@ -298,6 +298,7 @@ class User(AbstractUser):
choices=QuickWatchDateChoices.choices,
help_text="Date to use when bulk-marking media as completed",
)
date_format = models.CharField(
max_length=20,
default=DateFormatChoices.ISO,
@@ -310,6 +311,13 @@ class User(AbstractUser):
choices=TimeFormatChoices.choices,
help_text="Preferred time display format",
)
# Progress bar
progress_bar = models.BooleanField(
default=False,
help_text="Show progress bar",
)
# Calendar preferences
calendar_layout = models.CharField(
max_length=20,

View File

@@ -237,6 +237,7 @@ def preferences(request):
"quick_watch_date",
QuickWatchDateChoices.CURRENT_DATE,
)
request.user.progress_bar = "progress_bar" in request.POST
request.user.date_format = request.POST.get(
"date_format",
DateFormatChoices.ISO,