mirror of
https://github.com/FuzzyGrim/Yamtrack.git
synced 2026-03-03 03:47:02 +00:00
Merge pull request #1130 from busliggabor/feature/progress_line
[Feature] Progress line on media items
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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.)."""
|
||||
|
||||
@@ -80,6 +80,7 @@ def progress_edit(request, media_type, instance_id):
|
||||
|
||||
context = {
|
||||
"media": media,
|
||||
"user": request.user,
|
||||
}
|
||||
return render(
|
||||
request,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
17
src/templates/app/components/progress_bar.html
Normal file
17
src/templates/app/components/progress_bar.html
Normal 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 %}
|
||||
@@ -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 %}
|
||||
@@ -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">
|
||||
|
||||
19
src/users/migrations/0044_user_progress_bar.py
Normal file
19
src/users/migrations/0044_user_progress_bar.py
Normal 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"),
|
||||
),
|
||||
]
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user