From 6ba187f8e431d38e4ae42465d18a2c91987d0c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Sun, 14 Jan 2024 21:27:14 +0100 Subject: [PATCH] Make it possible to end session from game overview --- CHANGELOG.md | 4 +- games/static/base.css | 181 ++++++++++++++++++++++------- games/templates/list_sessions.html | 10 +- games/templates/view_game.html | 46 ++++++-- games/urls.py | 27 +++-- games/views.py | 55 ++++----- 6 files changed, 226 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a2e9a..7778332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ ## Unreleased ## Improved -* game overview: improve how editions and purchases are displayed +* game overview: + * improve how editions and purchases are displayed + * make it possible to end session from overview * add purchase: only allow choosing purchases of selected edition * session list: * starting and ending sessions is much faster/doest not reload the page diff --git a/games/static/base.css b/games/static/base.css index 328824a..f41c551 100644 --- a/games/static/base.css +++ b/games/static/base.css @@ -1,5 +1,5 @@ /* -! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.0 | MIT License | https://tailwindcss.com */ /* @@ -32,9 +32,11 @@ 4. Use the user's configured `sans` font-family by default. 5. Use the user's configured `sans` font-feature-settings by default. 6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS */ -html { +html, +:host { line-height: 1.5; /* 1 */ -webkit-text-size-adjust: 100%; @@ -44,12 +46,14 @@ html { -o-tab-size: 4; tab-size: 4; /* 3 */ - font-family: IBM Plex Sans, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-family: IBM Plex Sans, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */ font-feature-settings: normal; /* 5 */ font-variation-settings: normal; /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ } /* @@ -121,8 +125,10 @@ strong { } /* -1. Use the user's configured `mono` font family by default. -2. Correct the odd `em` font sizing in all browsers. +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. */ code, @@ -131,8 +137,12 @@ samp, pre { font-family: IBM Plex Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */ - font-size: 1em; + font-feature-settings: normal; /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ } /* @@ -567,10 +577,26 @@ select { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); } +@media (forced-colors: active) { + [type='checkbox']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + [type='radio']:checked { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); } +@media (forced-colors: active) { + [type='radio']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + [type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus { border-color: transparent; background-color: currentColor; @@ -585,6 +611,14 @@ select { background-repeat: no-repeat; } +@media (forced-colors: active) { + [type='checkbox']:indeterminate { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + [type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { border-color: transparent; background-color: currentColor; @@ -800,6 +834,11 @@ select { margin-bottom: 1.5rem; } +.mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; +} + .mb-1 { margin-bottom: 0.25rem; } @@ -868,6 +907,18 @@ select { height: 1.5rem; } +.h-8 { + height: 2rem; +} + +.h-2 { + height: 0.5rem; +} + +.h-3 { + height: 0.75rem; +} + .min-h-screen { min-height: 100vh; } @@ -884,6 +935,10 @@ select { width: 1.75rem; } +.w-8 { + width: 2rem; +} + .w-auto { width: auto; } @@ -892,6 +947,14 @@ select { width: 100%; } +.w-4 { + width: 1rem; +} + +.w-3 { + width: 0.75rem; +} + .max-w-screen-lg { max-width: 1024px; } @@ -904,6 +967,10 @@ select { max-width: 20rem; } +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + @keyframes spin { to { transform: rotate(360deg); @@ -938,6 +1005,12 @@ select { gap: 0.5rem; } +.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))); +} + .self-center { align-self: center; } @@ -964,6 +1037,10 @@ select { border-radius: 0.125rem; } +.rounded-full { + border-radius: 9999px; +} + .border-gray-200 { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); @@ -994,6 +1071,16 @@ select { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } +.bg-green-400 { + --tw-bg-opacity: 1; + background-color: rgb(74 222 128 / var(--tw-bg-opacity)); +} + +.bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); +} + .p-4 { padding: 1rem; } @@ -1018,6 +1105,16 @@ select { padding-bottom: 0.5rem; } +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + .pl-3 { padding-left: 0.75rem; } @@ -1179,7 +1276,7 @@ a:hover { transition: all 0.2s ease-out; } -:is(.dark form label) { +:is(:where(.dark) form label) { --tw-text-opacity: 1; color: rgb(148 163 184 / var(--tw-text-opacity)); } @@ -1189,7 +1286,7 @@ a:hover { margin-right: auto; } -:is(.dark .responsive-table) { +:is(:where(.dark) .responsive-table) { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); } @@ -1220,8 +1317,8 @@ a:hover { border-left-color: rgb(100 116 139 / var(--tw-border-opacity)); } -:is(.dark form input),:is(.dark -select),:is(.dark +:is(:where(.dark) form input),:is(:where(.dark) +select),:is(:where(.dark) textarea) { border-width: 1px; --tw-border-opacity: 1; @@ -1232,8 +1329,8 @@ textarea) { color: rgb(241 245 249 / var(--tw-text-opacity)); } -:is(.dark form input:disabled),:is(.dark -select:disabled),:is(.dark +:is(:where(.dark) form input:disabled),:is(:where(.dark) +select:disabled),:is(:where(.dark) textarea:disabled) { --tw-bg-opacity: 1; background-color: rgb(51 65 85 / var(--tw-bg-opacity)); @@ -1405,36 +1502,6 @@ th label { display: block; } -:is(.dark .dark\:bg-gray-800) { - --tw-bg-opacity: 1; - background-color: rgb(31 41 55 / var(--tw-bg-opacity)); -} - -:is(.dark .dark\:bg-gray-900) { - --tw-bg-opacity: 1; - background-color: rgb(17 24 39 / var(--tw-bg-opacity)); -} - -:is(.dark .dark\:text-slate-400) { - --tw-text-opacity: 1; - color: rgb(148 163 184 / var(--tw-text-opacity)); -} - -:is(.dark .dark\:text-slate-500) { - --tw-text-opacity: 1; - color: rgb(100 116 139 / var(--tw-text-opacity)); -} - -:is(.dark .dark\:text-slate-600) { - --tw-text-opacity: 1; - color: rgb(71 85 105 / var(--tw-text-opacity)); -} - -:is(.dark .dark\:text-white) { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); -} - @media (min-width: 640px) { .sm\:inline { display: inline; @@ -1519,3 +1586,33 @@ th label { max-width: 32rem; } } + +:is(:where(.dark) .dark\:bg-gray-800) { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +:is(:where(.dark) .dark\:bg-gray-900) { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +:is(:where(.dark) .dark\:text-slate-400) { + --tw-text-opacity: 1; + color: rgb(148 163 184 / var(--tw-text-opacity)); +} + +:is(:where(.dark) .dark\:text-slate-500) { + --tw-text-opacity: 1; + color: rgb(100 116 139 / var(--tw-text-opacity)); +} + +:is(:where(.dark) .dark\:text-slate-600) { + --tw-text-opacity: 1; + color: rgb(71 85 105 / var(--tw-text-opacity)); +} + +:is(:where(.dark) .dark\:text-white) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} diff --git a/games/templates/list_sessions.html b/games/templates/list_sessions.html index 26b1670..70de2ae 100644 --- a/games/templates/list_sessions.html +++ b/games/templates/list_sessions.html @@ -5,10 +5,11 @@ {% endblock title %} {% block content %} {% if dataset_count >= 1 %} + {% url 'list_sessions_start_session_from_session' last.id as start_session_url %}
{% if not session.timestamp_end %} - Sessions ({{ session_count }}) - {% url 'start_game_session' game.id as add_session_link %} - {% include 'components/button.html' with title="Start new session" text="New" link=add_session_link %} + {% url 'view_game_start_session_from_session' latest_session_id as add_session_link %} + New and Notes ({{ sessions_with_notes_count }}) -
diff --git a/games/urls.py b/games/urls.py index 9c6c706..87d6dde 100644 --- a/games/urls.py +++ b/games/urls.py @@ -19,19 +19,28 @@ urlpatterns = [ name="add_session_for_purchase", ), path( - "update-session/by-session/", - views.update_session, - name="update_session", + "session/clone/from-game/", + views.new_session_from_existing_session, + {"template": "view_game.html#session-info"}, + name="view_game_start_session_from_session", ), path( - "start-session-same-as-last/", - views.start_session_same_as_last, - name="start_session_same_as_last", + "session/clone/from-list/", + views.new_session_from_existing_session, + {"template": "list_sessions.html#session-row"}, + name="list_sessions_start_session_from_session", ), path( - "start-session/", - views.start_game_session, - name="start_game_session", + "session/end/from-game/", + views.end_session, + {"template": "view_game.html#session-info"}, + name="view_game_end_session", + ), + path( + "session/end/from-list/", + views.end_session, + {"template": "list_sessions.html#session-row"}, + name="list_sessions_end_session", ), # path( # "delete_session/by-id/", diff --git a/games/views.py b/games/views.py index d8f145e..467d9b5 100644 --- a/games/views.py +++ b/games/views.py @@ -82,16 +82,6 @@ def add_session(request, purchase_id=None): return render(request, "add_session.html", context) -def update_session(request, session_id=None): - session = get_object_or_404(Session, id=session_id) - session.finish_now() - session.save() - if request.htmx: - context = {"session": session} - return render(request, "list_sessions.html#session-row", context) - return redirect("list_sessions") - - def use_custom_redirect( func: Callable[..., HttpResponse] ) -> Callable[..., HttpResponse]: @@ -176,7 +166,8 @@ def view_game(request, game_id=None): session_count = sessions.count() playrange_start = sessions.earliest().timestamp_start.strftime("%b %Y") - playrange_end = sessions.latest().timestamp_start.strftime("%b %Y") + latest_session = sessions.latest() + playrange_end = latest_session.timestamp_start.strftime("%b %Y") playrange = ( playrange_start @@ -197,6 +188,7 @@ def view_game(request, game_id=None): "sessions": sessions.order_by("-timestamp_start"), "title": f"Game Overview - {game.name}", "hours_sum": total_hours, + "latest_session_id": latest_session.pk, } request.session["return_path"] = request.path @@ -240,33 +232,34 @@ def related_purchase_by_edition(request): return render(request, "partials/related_purchase_field.html", {"form": form}) +def clone_session_by_id(session_id: int) -> Session: + session = get_object_or_404(Session, id=session_id) + clone = session + clone.pk = None + clone.timestamp_start = timezone.now() + clone.timestamp_end = None + clone.note = "" + clone.save() + return clone + + @use_custom_redirect -def start_game_session(request, game_id: int): - last_session = Session.objects.filter(purchase__edition__game_id=game_id).latest() - session = SessionForm( - { - "purchase": last_session.purchase.id, - "timestamp_start": timezone.now(), - "device": last_session.device, - } - ) - session.save() +def new_session_from_existing_session(request, session_id: int, template: str = ""): + session = clone_session_by_id(session_id) + if request.htmx: + context = {"session": session} + return render(request, template, context) return redirect("list_sessions") -def start_session_same_as_last(request, last_session_id: int): - last_session = get_object_or_404(Session, id=last_session_id) - # clone it - session = last_session - session.pk = None - # set new data - session.timestamp_start = timezone.now() - session.timestamp_end = None - session.note = "" +@use_custom_redirect +def end_session(request, session_id: int, template: str = ""): + session = get_object_or_404(Session, id=session_id) + session.timestamp_end = timezone.now() session.save() if request.htmx: context = {"session": session} - return render(request, "list_sessions.html#session-row", context) + return render(request, template, context) return redirect("list_sessions")