Improve game links
Django CI/CD / test (push) Successful in 1m6s Details
Django CI/CD / build-and-push (push) Successful in 1m56s Details

This commit is contained in:
Lukáš Kucharczyk 2024-07-09 19:40:47 +02:00
parent 86f8fde8fa
commit ba44814474
Signed by: lukas
SSH Key Fingerprint: SHA256:vMuSwvwAvcT6htVAioMP7rzzwMQNi3roESyhv+nAxeg
11 changed files with 263 additions and 54 deletions

View File

@ -15,6 +15,7 @@
* ignore manual sessions when calculating session average
* stats: improve purchase name consistency
* session list: use display name instead of sort name
* unify the appearance of game links, and make them expand to full size on hover
## Fixed
* Fix title not being displayed on the Recent sessions page

View File

@ -23,18 +23,18 @@
font-style: normal;
}
a:hover {
/* a:hover {
text-decoration-color: #ff4400;
color: rgb(254, 185, 160);
transition: all 0.2s ease-out;
}
} */
form label {
@apply dark:text-slate-400;
}
.responsive-table {
@apply dark:text-white mx-auto;
@apply dark:text-white mx-auto table-fixed;
}
.responsive-table tr:nth-child(even) {
@ -58,8 +58,11 @@ form label {
.max-w-20char {
max-width: 20ch;
}
.max-w-30char {
max-width: 30ch;
}
.max-w-35char {
max-width: 40ch;
max-width: 35ch;
}
.max-w-40char {
max-width: 40ch;
@ -145,3 +148,13 @@ th label {
margin-bottom: 0.5em;
padding-left: 1em;
}
.truncate-container {
@apply inline-block relative transition-all;
a {
@apply inline-block truncate max-w-20char;
&:hover {
@apply absolute max-w-none -top-8 -left-6 min-w-60 px-6 py-3.5 bg-purple-600 rounded-sm outline-dashed outline-purple-400 outline-4;
}
}
}

View File

@ -1,5 +1,5 @@
/*
! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com
! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com
*/
/*
@ -783,6 +783,18 @@ select {
}
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.invisible {
visibility: hidden;
}
@ -819,6 +831,14 @@ select {
top: 0.75rem;
}
.-left-6 {
left: -1.5rem;
}
.-top-\[31\.5px\] {
top: -31.5px;
}
.mx-2 {
margin-left: 0.5rem;
margin-right: 0.5rem;
@ -860,6 +880,10 @@ select {
margin-bottom: 2rem;
}
.me-2 {
margin-inline-end: 0.5rem;
}
.ml-1 {
margin-left: 0.25rem;
}
@ -892,6 +916,10 @@ select {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.table {
display: table;
}
@ -900,12 +928,12 @@ select {
display: none;
}
.h-24 {
height: 6rem;
.h-12 {
height: 3rem;
}
.h-3 {
height: 0.75rem;
.h-24 {
height: 6rem;
}
.h-4 {
@ -924,6 +952,10 @@ select {
min-height: 100vh;
}
.w-24 {
width: 6rem;
}
.w-5 {
width: 1.25rem;
}
@ -944,6 +976,10 @@ select {
width: 100%;
}
.min-w-60 {
min-width: 15rem;
}
.max-w-screen-lg {
max-width: 1024px;
}
@ -1036,6 +1072,11 @@ select {
border-color: rgb(100 116 139 / var(--tw-border-opacity));
}
.bg-blue-700 {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
}
.bg-gray-200 {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
@ -1051,6 +1092,14 @@ select {
background-color: rgb(124 58 237 / var(--tw-bg-opacity));
}
.p-2 {
padding: 0.5rem;
}
.p-2\.5 {
padding: 0.625rem;
}
.p-4 {
padding: 1rem;
}
@ -1075,6 +1124,16 @@ select {
padding-bottom: 0.5rem;
}
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.py-\[13\.5px\] {
padding-top: 13.5px;
padding-bottom: 13.5px;
}
.pb-16 {
padding-bottom: 4rem;
}
@ -1099,6 +1158,10 @@ select {
text-align: center;
}
.align-top {
vertical-align: top;
}
.font-mono {
font-family: IBM Plex Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
@ -1128,6 +1191,11 @@ select {
line-height: 1.75rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
@ -1138,6 +1206,10 @@ select {
line-height: 1rem;
}
.font-medium {
font-weight: 500;
}
.font-semibold {
font-weight: 600;
}
@ -1244,11 +1316,11 @@ select {
font-style: normal;
}
a:hover {
/* a:hover {
text-decoration-color: #ff4400;
color: rgb(254, 185, 160);
transition: all 0.2s ease-out;
}
} */
form label:is(.dark *) {
--tw-text-opacity: 1;
@ -1258,6 +1330,7 @@ form label:is(.dark *) {
.responsive-table {
margin-left: auto;
margin-right: auto;
table-layout: fixed;
}
.responsive-table:is(.dark *) {
@ -1448,6 +1521,71 @@ th label {
padding-left: 1em;
}
.truncate-container {
position: relative;
display: inline-block;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
a {
display: inline-block;
}
a {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
a {
max-width: 20ch;
}
a {
&:hover {
position: absolute;
}
&:hover {
top: -2rem;
}
&:hover {
left: -1.5rem;
}
&:hover {
min-width: 15rem;
}
&:hover {
max-width: none;
}
&:hover {
border-radius: 0.125rem;
}
&:hover {
--tw-bg-opacity: 1;
background-color: rgb(147 51 234 / var(--tw-bg-opacity));
}
&:hover {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
&:hover {
padding-top: 0.875rem;
padding-bottom: 0.875rem;
}
&:hover {
outline-style: dashed;
}
&:hover {
outline-width: 4px;
}
&:hover {
outline-color: #c084fc;
}
}
}
.hover\:bg-blue-800:hover {
--tw-bg-opacity: 1;
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
}
.hover\:bg-gray-400:hover {
--tw-bg-opacity: 1;
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
@ -1478,6 +1616,17 @@ th label {
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.focus\:ring-4:focus {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.focus\:ring-blue-300:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
}
.focus\:ring-green-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(34 197 94 / var(--tw-ring-opacity));
@ -1504,6 +1653,11 @@ th label {
display: block;
}
.dark\:bg-blue-600:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
.dark\:bg-gray-800:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
@ -1534,6 +1688,16 @@ th label {
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.dark\:hover\:bg-blue-700:hover:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
}
.dark\:focus\:ring-blue-800:focus:is(.dark *) {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(30 64 175 / var(--tw-ring-opacity));
}
@media (min-width: 640px) {
.sm\:inline {
display: inline;
@ -1599,10 +1763,6 @@ th label {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.md\:max-w-40char {
max-width: 40ch;
}
}
@media (min-width: 1024px) {

View File

@ -0,0 +1,2 @@
components:
gamelink: "components/game_link.html"

View File

@ -0,0 +1,9 @@
<span class="truncate-container">
<a class="underline decoration-slate-500 sm:decoration-2" href="{% url 'view_game' game_id %}">
{% if children %}
{{ children }}
{% else %}
{{ name }}
{% endif %}
</a>
</span>

View File

@ -33,11 +33,8 @@
{% for session in dataset %}
{% partialdef session-row inline=True %}
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name truncate max-w-20char md:max-w-40char">
<a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'view_game' session.purchase.edition.game.id %}">
{{ session.purchase.edition.name }}
</a>
<td class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name relative align-top w-24 h-12">
{% gamelink game_id=session.purchase.edition.game.id name=session.purchase.edition.name %}
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden sm:table-cell">
{{ session.timestamp_start | date:"d/m/Y H:i" }}

View File

@ -6,9 +6,11 @@
{% partialdef purchase-name %}
{% if purchase.type != 'game' %}
{{ purchase.name }} ({{ purchase.edition.name }} {{ purchase.get_type_display }})
{% #gamelink game_id=purchase.edition.game.id %}
{{ purchase.name }} ({{ purchase.edition.name }} {{ purchase.get_type_display }})
{% /gamelink %}
{% else %}
{{ purchase.edition.name }}
{% gamelink game_id=purchase.edition.game.id name=purchase.edition.name %}
{% endif %}
{% endpartialdef %}
@ -60,25 +62,25 @@
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Longest session</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ longest_session_time }} ({{ longest_session_game }})</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ longest_session_time }} ({% gamelink game_id=longest_session_game.id name=longest_session_game.name %})</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Most sessions</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ highest_session_count }} ({{ highest_session_count_game }})</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ highest_session_count }} ({% gamelink game_id=highest_session_count_game.id name=highest_session_count_game.name %})</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Highest session average</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
{{ highest_session_average }} ({{ highest_session_average_game }})
{{ highest_session_average }} ({% gamelink game_id=highest_session_average_game.id name=highest_session_average_game.name %})
</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">First play</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ first_play_name }} ({{ first_play_date }})</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{% gamelink game_id=first_play_game.id name=first_play_game.name %} ({{ first_play_date }})</td>
</tr>
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Last play</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ last_play_name }} ({{ last_play_date }})</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{% gamelink game_id=last_play_game.id name=last_play_game.name %} ({{ last_play_date }})</td>
</tr>
</tbody>
</table>
@ -142,8 +144,7 @@
{% for game in top_10_games_by_playtime %}
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'view_game' game.id %}">{{ game.name }}</a>
{% gamelink game_id=game.id name=game.name %}
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ game.formatted_playtime }}</td>
</tr>
@ -179,10 +180,7 @@
{% for purchase in all_finished_this_year %}
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'edit_purchase' purchase.id %}">
{% partial purchase-name %}
</a>
{% partial purchase-name %}
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td>
</tr>
@ -201,8 +199,7 @@
{% for purchase in this_year_finished_this_year %}
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a>
{% partial purchase-name %}
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td>
</tr>
@ -221,8 +218,7 @@
{% for purchase in purchased_this_year_finished_this_year %}
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a>
{% partial purchase-name %}
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td>
</tr>
@ -243,10 +239,7 @@
{% for purchase in purchased_unfinished %}
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'edit_purchase' purchase.id %}">
{% partial purchase-name %}
</a>
{% partial purchase-name %}
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.price }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_purchased | date:"d/m/Y" }}</td>
@ -268,10 +261,7 @@
{% for purchase in all_purchased_this_year %}
<tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'edit_purchase' purchase.id %}">
{% partial purchase-name %}
</a>
{% partial purchase-name %}
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.price }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_purchased | date:"d/m/Y" }}</td>

View File

@ -504,10 +504,10 @@ def stats(request, year: int = 0):
last_play_date = "N/A"
if this_year_sessions:
first_session = this_year_sessions.earliest()
first_play_name = first_session.purchase.edition.name
first_play_game = first_session.purchase.edition.game
first_play_date = first_session.timestamp_start.strftime("%x")
last_session = this_year_sessions.latest()
last_play_name = last_session.purchase.edition.name
last_play_game = last_session.purchase.edition.game
last_play_date = last_session.timestamp_start.strftime("%x")
all_purchased_this_year_count = this_year_purchases_with_currency.count()
@ -578,7 +578,7 @@ def stats(request, year: int = 0):
else 0
),
"longest_session_game": (
longest_session.purchase.edition.name if longest_session else "N/A"
longest_session.purchase.edition.game if longest_session else None
),
"highest_session_count": (
game_highest_session_count.session_count
@ -586,7 +586,7 @@ def stats(request, year: int = 0):
else 0
),
"highest_session_count_game": (
game_highest_session_count.name if game_highest_session_count else "N/A"
game_highest_session_count if game_highest_session_count else None
),
"highest_session_average": (
format_duration(
@ -596,9 +596,9 @@ def stats(request, year: int = 0):
else 0
),
"highest_session_average_game": highest_session_average_game,
"first_play_name": first_play_name,
"first_play_game": first_play_game,
"first_play_date": first_play_date,
"last_play_name": last_play_name,
"last_play_game": last_play_game,
"last_play_date": last_play_date,
"title": f"{year} Stats",
"month_playtimes": month_playtimes,

34
poetry.lock generated
View File

@ -829,6 +829,23 @@ files = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "slippers"
version = "0.6.2"
description = "Build reusable components in Django without writing a single line of Python."
optional = false
python-versions = ">=3.8.0"
files = [
{file = "slippers-0.6.2-py3-none-any.whl", hash = "sha256:739e05f85354becbf0a65daab831eea62557d89e7512042209ab629af4378bca"},
{file = "slippers-0.6.2.tar.gz", hash = "sha256:4cb555b8822ba0d404e5405723f5d723994022c29046008ee917081031bc0cf1"},
]
[package.dependencies]
Django = ">=3.2"
PyYAML = ">=5.4.0"
typeguard = ">=2.13.3,<3.0.0"
typing-extensions = ">=4.4.0"
[[package]]
name = "sqlparse"
version = "0.5.0"
@ -875,6 +892,21 @@ notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
[[package]]
name = "typeguard"
version = "2.13.3"
description = "Run-time type checker for Python"
optional = false
python-versions = ">=3.5.3"
files = [
{file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"},
{file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"},
]
[package.extras]
doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["mypy", "pytest", "typing-extensions"]
[[package]]
name = "typing-extensions"
version = "4.12.2"
@ -938,4 +970,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "f82f589e57bb158d66df005669003bd51084d5eac795ca66201d8b991d1a8b61"
content-hash = "ca9188453f62ec4470e2b2f8bc1e4a17ad421272f401328f82d6fede231dc737"

View File

@ -31,6 +31,7 @@ django-template-partials = "^24.2"
markdown = "^3.6"
slippers = "^0.6.2"
[tool.isort]
profile = "black"

View File

@ -41,6 +41,7 @@ INSTALLED_APPS = [
"template_partials",
"graphene_django",
"django_htmx",
"slippers",
]
GRAPHENE = {"SCHEMA": "games.schema.schema"}
@ -85,7 +86,10 @@ TEMPLATES = [
"games.views.model_counts",
"games.views.stats_dropdown_year_range",
],
"builtins": ["template_partials.templatetags.partials"],
"builtins": [
"template_partials.templatetags.partials",
"slippers.templatetags.slippers",
],
},
},
]