From e067e65bce9c2d6d2a5e257905a0c4a54ef0e667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Mon, 2 Sep 2024 20:04:21 +0200 Subject: [PATCH] linkify game, edition, purchase, session references also add link styles for links in a table row --- common/utils.py | 14 ++ games/static/base.css | 183 +++++++++++++++----------- games/templates/cotton/table_row.html | 2 +- games/views/edition.py | 15 ++- games/views/game.py | 15 ++- games/views/purchase.py | 15 ++- games/views/session.py | 15 ++- 7 files changed, 174 insertions(+), 85 deletions(-) diff --git a/common/utils.py b/common/utils.py index 6a5ec38..264c9ca 100644 --- a/common/utils.py +++ b/common/utils.py @@ -27,6 +27,20 @@ def Popover( return result +HTMLAttribute = tuple[str, str] +HTMLTag = str + + +def A(attributes: list[HTMLAttribute], children: list[HTMLTag] | HTMLTag) -> HTMLTag: + if isinstance(children, str): + children = [children] + childrenBlob = "\n".join(children) + attributesList = [f'{name} = "{value}"' for name, value in attributes] + attributesBlob = " ".join(attributesList) + tag: str = f"{childrenBlob}" + return mark_safe(tag) + + def safe_division(numerator: int | float, denominator: int | float) -> int | float: """ Divides without triggering division by zero exception. diff --git a/games/static/base.css b/games/static/base.css index ba8af4f..98794d4 100644 --- a/games/static/base.css +++ b/games/static/base.css @@ -1386,6 +1386,10 @@ input:checked + .toggle-bg { margin-bottom: 1rem; } +.mb-6 { + margin-bottom: 1.5rem; +} + .mb-8 { margin-bottom: 2rem; } @@ -1394,10 +1398,6 @@ input:checked + .toggle-bg { margin-inline-end: 0.5rem; } -.ml-1 { - margin-left: 0.25rem; -} - .mr-4 { margin-right: 1rem; } @@ -1422,14 +1422,6 @@ input:checked + .toggle-bg { margin-top: 1rem; } -.mb-5 { - margin-bottom: 1.25rem; -} - -.mb-6 { - margin-bottom: 1.5rem; -} - .block { display: block; } @@ -1479,10 +1471,6 @@ input:checked + .toggle-bg { height: 0.625rem; } -.h-3 { - height: 0.75rem; -} - .h-4 { height: 1rem; } @@ -1543,10 +1531,6 @@ input:checked + .toggle-bg { width: 16rem; } -.w-7 { - width: 1.75rem; -} - .w-full { width: 100%; } @@ -1705,12 +1689,6 @@ input:checked + .toggle-bg { margin-left: calc(-1px * calc(1 - var(--tw-space-x-reverse))); } -.space-x-1 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(0.25rem * var(--tw-space-x-reverse)); - margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); -} - .space-x-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.5rem * var(--tw-space-x-reverse)); @@ -1764,10 +1742,6 @@ input:checked + .toggle-bg { border-radius: 0.25rem; } -.rounded-full { - border-radius: 9999px; -} - .rounded-lg { border-radius: 0.5rem; } @@ -1886,11 +1860,6 @@ input:checked + .toggle-bg { background-color: rgb(5 122 85 / var(--tw-bg-opacity)); } -.bg-violet-600 { - --tw-bg-opacity: 1; - background-color: rgb(124 58 237 / var(--tw-bg-opacity)); -} - .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -2000,10 +1969,6 @@ input:checked + .toggle-bg { vertical-align: top; } -.font-condensed { - font-family: IBM Plex Sans Condensed, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; -} - .font-mono { font-family: IBM Plex Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } @@ -2174,6 +2139,18 @@ input:checked + .toggle-bg { text-decoration-color: #64748b; } +.decoration-wavy { + text-decoration-style: wavy; +} + +.underline-offset-2 { + text-underline-offset: 2px; +} + +.underline-offset-4 { + text-underline-offset: 4px; +} + .opacity-0 { opacity: 0; } @@ -2239,6 +2216,12 @@ input:checked + .toggle-bg { transition-duration: 150ms; } +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + .duration-200 { transition-duration: 200ms; } @@ -2247,6 +2230,14 @@ input:checked + .toggle-bg { transition-duration: 300ms; } +.duration-1000 { + transition-duration: 1000ms; +} + +.duration-500 { + transition-duration: 500ms; +} + .ease-in { transition-timing-function: cubic-bezier(0.4, 0, 1, 1); } @@ -2573,11 +2564,6 @@ textarea:disabled:is(.dark *) { background-color: rgb(240 82 82 / var(--tw-bg-opacity)); } -.hover\:bg-violet-700:hover { - --tw-bg-opacity: 1; - background-color: rgb(109 40 217 / var(--tw-bg-opacity)); -} - .hover\:bg-white:hover { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -2613,6 +2599,10 @@ textarea:disabled:is(.dark *) { color: rgb(255 255 255 / var(--tw-text-opacity)); } +.hover\:decoration-red-600:hover { + text-decoration-color: #E02424; +} + .focus\:z-10:focus { z-index: 10; } @@ -2664,11 +2654,6 @@ textarea:disabled:is(.dark *) { --tw-ring-color: rgb(4 108 78 / var(--tw-ring-opacity)); } -.focus\:ring-violet-500:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(139 92 246 / var(--tw-ring-opacity)); -} - .focus\:ring-offset-2:focus { --tw-ring-offset-width: 2px; } @@ -2677,10 +2662,6 @@ textarea:disabled:is(.dark *) { --tw-ring-offset-color: #C3DDFD; } -.focus\:ring-offset-violet-200:focus { - --tw-ring-offset-color: #ddd6fe; -} - .group:hover .group-hover\:absolute { position: absolute; } @@ -2848,11 +2829,6 @@ textarea:disabled:is(.dark *) { color: rgb(148 163 184 / var(--tw-text-opacity)); } -.dark\:text-slate-500:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(100 116 139 / var(--tw-text-opacity)); -} - .dark\:text-slate-600:is(.dark *) { --tw-text-opacity: 1; color: rgb(71 85 105 / var(--tw-text-opacity)); @@ -2979,14 +2955,6 @@ textarea:disabled:is(.dark *) { padding-right: 1rem; } - .sm\:pl-2 { - padding-left: 0.5rem; - } - - .sm\:pl-4 { - padding-left: 1rem; - } - .sm\:decoration-2 { text-decoration-thickness: 2px; } @@ -3058,11 +3026,6 @@ textarea:disabled:is(.dark *) { padding-bottom: 0.5rem; } - .md\:text-5xl { - font-size: 3rem; - line-height: 1; - } - .md\:text-blue-700 { --tw-text-opacity: 1; color: rgb(26 86 219 / var(--tw-text-opacity)); @@ -3113,11 +3076,6 @@ textarea:disabled:is(.dark *) { .lg\:max-w-lg { max-width: 32rem; } - - .lg\:text-6xl { - font-size: 3.75rem; - line-height: 1; - } } @media (min-width: 1536px) { @@ -3152,3 +3110,76 @@ textarea:disabled:is(.dark *) { border-start-end-radius: 0.5rem; border-end-end-radius: 0.5rem; } + +.\[\&\>a\]\:underline>a { + text-decoration-line: underline; +} + +.\[\&_a\]\:underline a { + text-decoration-line: underline; +} + +.\[\&_a\]\:decoration-double a { + text-decoration-style: double; +} + +.\[\&_a\]\:decoration-dashed a { + text-decoration-style: dashed; +} + +.\[\&_a\]\:decoration-wavy a { + text-decoration-style: wavy; +} + +.\[\&_a\]\:decoration-2 a { + text-decoration-thickness: 2px; +} + +.\[\&_a\]\:underline-offset-4 a { + text-underline-offset: 4px; +} + +.\[\&_a\]\:hover\:bg-purple-800:hover a { + --tw-bg-opacity: 1; + background-color: rgb(85 33 181 / var(--tw-bg-opacity)); +} + +.\[\&_a\]\:hover\:p-5:hover a { + padding: 1.25rem; +} + +.\[\&_a\]\:hover\:p-1:hover a { + padding: 0.25rem; +} + +.\[\&_a\]\:hover\:decoration-red-600:hover a { + text-decoration-color: #E02424; +} + +.\[\&_a\]\:hover\:decoration-purple-600:hover a { + text-decoration-color: #7E3AF2; +} + +.\[\&_a\]\:hover\:decoration-purple-300:hover a { + text-decoration-color: #CABFFD; +} + +.\[\&_a\]\:hover\:decoration-purple-400:hover a { + text-decoration-color: #AC94FA; +} + +.\[\&_a\]\:hover\:decoration-purple-500:hover a { + text-decoration-color: #9061F9; +} + +.\[\&_a\]\:hover\:decoration-purple-900:hover a { + text-decoration-color: #4A1D96; +} + +.\[\&_a\]\:hover\:decoration-gray-800:hover a { + text-decoration-color: #1F2937; +} + +.\[\&_a\]\:hover\:underline-offset-8:hover a { + text-underline-offset: 8px; +} diff --git a/games/templates/cotton/table_row.html b/games/templates/cotton/table_row.html index 4ca4622..cd29516 100644 --- a/games/templates/cotton/table_row.html +++ b/games/templates/cotton/table_row.html @@ -1,4 +1,4 @@ - + {% if slot %} {{ slot }} {% else %} diff --git a/games/views/edition.py b/games/views/edition.py index d2d551d..f569390 100644 --- a/games/views/edition.py +++ b/games/views/edition.py @@ -7,7 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.template.loader import render_to_string from django.urls import reverse -from common.utils import truncate_with_popover +from common.utils import A, truncate_with_popover from games.forms import EditionForm from games.models import Edition, Game from games.views.general import dateformat @@ -48,7 +48,18 @@ def list_editions(request: HttpRequest) -> HttpResponse: ], "rows": [ [ - truncate_with_popover(edition.game.name), + A( + [ + ( + "href", + reverse( + "view_game", + args=[edition.game.pk], + ), + ) + ], + truncate_with_popover(edition.game.name), + ), truncate_with_popover( edition.name if edition.game.name != edition.name diff --git a/games/views/game.py b/games/views/game.py index ea1ae76..51132e7 100644 --- a/games/views/game.py +++ b/games/views/game.py @@ -9,7 +9,7 @@ from django.template.loader import render_to_string from django.urls import reverse from common.time import format_duration -from common.utils import safe_division, truncate_with_popover +from common.utils import A, safe_division, truncate_with_popover from games.forms import GameForm from games.models import Edition, Game, Purchase, Session from games.views.general import ( @@ -55,7 +55,18 @@ def list_games(request: HttpRequest) -> HttpResponse: ], "rows": [ [ - truncate_with_popover(game.name), + A( + [ + ( + "href", + reverse( + "view_game", + args=[game.pk], + ), + ) + ], + truncate_with_popover(game.name), + ), truncate_with_popover( game.sort_name if game.sort_name is not None and game.name != game.sort_name diff --git a/games/views/purchase.py b/games/views/purchase.py index 1abaf13..e75d338 100644 --- a/games/views/purchase.py +++ b/games/views/purchase.py @@ -13,7 +13,7 @@ from django.template.loader import render_to_string from django.urls import reverse from django.utils import timezone -from common.utils import truncate_with_popover +from common.utils import A, truncate_with_popover from games.forms import PurchaseForm from games.models import Edition, Purchase from games.views.general import dateformat, use_custom_redirect @@ -57,7 +57,18 @@ def list_purchases(request: HttpRequest) -> HttpResponse: ], "rows": [ [ - truncate_with_popover(purchase.edition.name), + A( + [ + ( + "href", + reverse( + "view_game", + args=[purchase.edition.game.pk], + ), + ), + ], + truncate_with_popover(purchase.edition.game.name), + ), purchase.platform, purchase.price, purchase.price_currency, diff --git a/games/views/session.py b/games/views/session.py index 16f37c7..9022a32 100644 --- a/games/views/session.py +++ b/games/views/session.py @@ -9,7 +9,7 @@ from django.urls import reverse from django.utils import timezone from common.time import format_duration -from common.utils import truncate_with_popover +from common.utils import A, truncate_with_popover from games.forms import SessionForm from games.models import Purchase, Session from games.views.general import ( @@ -56,7 +56,18 @@ def list_sessions(request: HttpRequest) -> HttpResponse: ], "rows": [ [ - truncate_with_popover(session.purchase.edition.name), + A( + [ + ( + "href", + reverse( + "view_game", + args=[session.purchase.edition.game.pk], + ), + ) + ], + truncate_with_popover(session.purchase.edition.name), + ), f"{session.timestamp_start.strftime(datetimeformat)}{f" — {session.timestamp_end.strftime(timeformat)}" if session.timestamp_end else ""}", ( format_duration(session.duration_calculated, durationformat)