remove trade offer expanding, fix flipping to work with new layout. all cards in trade offer are not expanded

This commit is contained in:
badblocks 2025-04-04 14:23:53 -07:00
parent c68d1fb5ec
commit 7c62c57433
11 changed files with 194 additions and 170 deletions

View file

@ -5,14 +5,14 @@ from django.utils.safestring import mark_safe
register = template.Library()
@register.inclusion_tag("templatetags/card_badge.html")
def card_badge(card, quantity=None, static=False):
def card_badge(card, quantity=None, expanded=False):
return {
'quantity': quantity,
'style': card.style,
'name': card.name,
'rarity': card.rarity_icon,
'cardset': card.cardset,
'static': static,
'expanded': expanded,
}
@register.filter
@ -26,6 +26,6 @@ def card_badge_inline(card, quantity=None):
'name': card.name,
'rarity': card.rarity_icon,
'cardset': card.cardset,
'static': True,
'expanded': True,
})
return mark_safe(html)

View file

@ -130,17 +130,55 @@ const $$ = selector => Array.from(document.querySelector(selector));
window.tradeOfferCard = function() {
return {
flipped: false,
badgeExpanded: false,
acceptanceExpanded: false,
/**
* Update the badge's expanded state.
show_back: false,
collapsed: false,
/*
* flipWithCollapse() now applies the height transition directly on the visible card face.
* It measures the current face (front or back) and the target face's height,
* then animates the current face's height change before toggling the flip.
*
* @param {boolean} expanded - The new state of the badge.
* Make sure your template markup includes:
* - x-ref="front" on the front card face
* - x-ref="back" on the back card face
*/
setBadge(expanded) {
this.badgeExpanded = expanded;
},
};
flipWithCollapse() {
// Determine the currently visible face and the target face.
const currentFace = this.flipped ? this.$refs.back : this.$refs.front;
const targetFace = this.flipped ? this.$refs.front : this.$refs.back;
const container = this.$refs.container;
// Temporarily force target face to display to measure its height.
const originalHeight = targetFace.style.height;
const originalDisplay = targetFace.style.display;
const originalPosition = targetFace.style.position;
const originalVisibility = targetFace.style.visibility;
targetFace.style.height = 'auto';
targetFace.style.display = 'block';
targetFace.style.position = 'absolute';
targetFace.style.visibility = 'hidden';
const targetHeight = targetFace.offsetHeight + 18;
targetFace.style.height = originalHeight;
targetFace.style.display = originalDisplay;
targetFace.style.position = originalPosition;
targetFace.style.visibility = originalVisibility;
container.setAttribute('x-collapse.duration.500ms.min.' + targetHeight + 'px', '');
this.collapsed = true;
setTimeout(() => {
this.show_back = this.flipped;
this.flipped = !this.flipped;
setTimeout(() => {
this.show_back = !this.show_back;
}, 250)
setTimeout(() => {
this.collapsed = false;
setTimeout(() => {
container.removeAttribute('x-collapse.duration.500ms.min.' + targetHeight + 'px');
}, 550);
}, 550);
}, 550);
}
}
};
}
})();

View file

@ -6,7 +6,7 @@
<div class="flex justify-center flex-wrap">
{% for card in group.cards %}
<a href="{% url 'cards:card_detail' card.pk %}">
{% card_badge card static=True %}
{% card_badge card expanded=True %}
</a>
{% endfor %}
</div>
@ -15,7 +15,7 @@
<div class="flex justify-center flex-wrap">
{% for card in cards %}
<a href="{% url 'cards:card_detail' card.pk %}">
{% card_badge card static=True %}
{% card_badge card expanded=True %}
</a>
{% endfor %}
</div>

View file

@ -4,7 +4,7 @@
{% for card in cards %}
<a href="{% url 'cards:card_detail' card.id %}"
class="flex justify-between items-center text-primary no-underline">
{% card_badge card quantity=card.offer_count static=True %}
{% card_badge card quantity=card.offer_count expanded=True %}
</a>
{% endfor %}
</div>

View file

@ -11,7 +11,7 @@
{# Render a trade acceptance using our new tag #}
{% render_trade_acceptance offer %}
{% else %}
{% render_trade_offer offer expanded=expanded %}
{% render_trade_offer offer %}
{% endif %}
</div>
{% empty %}

View file

@ -7,7 +7,7 @@
<div class="container mx-auto max-w-2xl mt-6">
<h2 class="text-2xl font-bold">Trade Offer Details</h2>
<div class="flex justify-center mt-10">
{% render_trade_offer object expanded=True %}
{% render_trade_offer object %}
</div>
{% if acceptance_form %}
<div class="w-3/4 mx-auto mt-4">

View file

@ -1,19 +1,19 @@
{% if not static %}
<div class="relative block">
<div :class="badgeExpanded ? 'grid grid-rows-2 grid-cols-4 h-[52px]' : 'flex flex-row items-center h-[32px]'" x-transition.duration.500ms class="p-1.5 w-40 text-white shadow-lg" style="{{ style }}">
<div :class="badgeExpanded ? 'row-start-1 col-start-1 col-span-3' : 'grow'" x-transition.duration.500ms class="truncate text-ellipsis font-semibold leading-tight text-sm">{{ name }}</div>
<div x-show="badgeExpanded" x-transition.duration.500ms x-cloak class="row-start-2 col-start-1 col-span-3 truncate align-bottom text-xs">{{ rarity }}</div>
<div x-show="true" x-transition.duration.500ms x-cloak :class="badgeExpanded ? 'row-start-2 col-start-4 col-span-1 align-bottom' : 'grow-0 shrink-0'" class="text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div>
{% if quantity != None %}<div x-show="true" x-transition.duration.500ms x-cloak :class="badgeExpanded ? 'row-start-1 col-start-4 justify-self-end -me-0.5' : 'grow-0 shrink-0'" class="relative w-fit ps-1"><div class="card-quantity-badge relative bg-gray-600 text-white text-sm font-semibold rounded-full text-center size-max px-1.5">{{ quantity }}</div></div>{% endif %}
{% if not expanded %}
<div class="relative block m-1">
<div class="flex flex-row items-center h-[32px] p-1.5 w-40 text-white shadow-lg" style="{{ style }}">
<div class="grow truncate text-ellipsis font-semibold leading-tight text-sm">{{ name }}</div>
<div class="grow-0 shrink-0 text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div>
{% if quantity != None %}<div class="grow-0 shrink-0 relative w-fit ps-1">
<div class="card-quantity-badge relative bg-gray-600 text-white text-sm font-semibold rounded-full text-center size-max px-1.5">{{ quantity }}</div></div>{% endif %}
</div>
</div>
{% else %}
<div class="relative inline-block m-1">
<div class="relative block m-1">
<div class="grid grid-rows-2 grid-cols-4 h-[52px] p-1.5 w-40 text-white shadow-lg" style="{{ style }}">
<div class="row-start-1 col-start-1 col-span-3 truncate text-ellipsis font-semibold leading-tight text-sm">{{ name }}</div>
<div class="row-start-2 col-start-1 col-span-3 truncate align-bottom text-xs">{{ rarity }}</div>
<div class="row-start-2 col-start-4 col-span-1 align-bottom text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div>
{% if quantity != None %}<div class="row-start-1 col-start-4 justify-self-end -me-0.5 relative w-fit ps-1"><div class="card-quantity-badge relative bg-gray-600 text-white text-sm font-semibold rounded-full text-center size-max px-1.5">{{ quantity }}</div></div>{% endif %}
<div class="row-start-1 col-start-1 col-span-3 truncate text-ellipsis self-start font-semibold leading-tight text-sm">{{ name }}</div>
<div class="row-start-2 col-start-1 col-span-3 truncate self-end text-xs">{{ rarity }}</div>
<div class="row-start-2 col-start-4 col-span-1 self-end text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div>
{% if quantity != None %}<div class="row-start-1 col-start-4 self-start justify-self-end -me-0.5 relative w-fit ps-1"><div class="card-quantity-badge relative bg-gray-600 text-white text-sm font-semibold rounded-full text-center size-max px-1.5">{{ quantity }}</div></div>{% endif %}
</div>
</div>
{% endif %}

View file

@ -1,25 +1,17 @@
{% load gravatar card_badge cache %}
{% url 'trade_offer_detail' pk=offer_pk as trade_offer_detail_url %}
{% cache 60 trade_offer offer_pk %}
<div class="trade-offer-card m-2 h-full w-auto flex justify-center">
<div x-data="tradeOfferCard()" x-init="defaultExpanded = {{expanded|lower}}; badgeExpanded = defaultExpanded; flipped = {{flipped|lower}}" class="trade-offer-card my-auto"
@toggle-all.window="setBadge($event.detail.expanded)">
<!-- Flip container providing perspective -->
<div class="flip-container" style="perspective: 1000px;">
<!--
The rotating element (.flip-inner) now uses CSS Grid to stack its children in a single cell.
Persistent border, shadow, and rounding are applied here and the card rotates entirely.
-->
<div class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg w-90 transform transition-transform duration-700 ease-in-out"
:class="{'rotate-y-180': flipped}">
<div x-data="tradeOfferCard()" x-init="flipped = {{flipped|lower}}; show_back = {{flipped|lower}}" class="trade-offer-card my-auto">
<div {% if request_path != trade_offer_detail_url %}@click="window.location.href = '{{ trade_offer_detail_url }}'"{% endif %} class="no-underline block flip-container{% if request_path != trade_offer_detail_url %} cursor-pointer{% endif %}" style="perspective: 1000px;">
<div x-ref="container" x-show="!collapsed" class="overflow-hidden flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg w-90 transform transition-transform duration-500 ease-in-out" :class="{'rotate-y-180': flipped}">
<!-- Front Face: Trade Offer -->
<!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap -->
<div x-show="!flipped" class="flip-face front col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between">
<div x-ref="front" x-show="!show_back" x-cloak class="flip-face front col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between">
<!-- Header -->
<div class="flip-face-header self-start">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<!-- Set this container as relative to position the avatar absolutely -->
<div class="relative mt-6 mb-4 mx-2 sm:mx-4">
<!-- Two-column grid for the labels -->
@ -36,48 +28,41 @@
</div>
</div>
</div>
</a>
</div>
<!-- Main Trade Offer Row -->
<div class="flip-face-body self-start">
{% if not flipped %}
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="px-2 main-badges pb-0">
<!-- Normal mode: just use an outer grid with 2 columns -->
<div class="flex flex-row justify-around">
<!-- Has Side -->
<div class="flex flex-col gap-y-2">
<div class="flex flex-col">
{% for card in have_cards_available %}
<a href="{% url 'cards:card_detail' card.pk %}">
{% card_badge card.card card.quantity %}
</a>
{% endfor %}
</div>
<!-- Wants Side -->
<div class="flex flex-col gap-y-2">
<div class="flex flex-col">
{% for card in want_cards_available %}
<a href="{% url 'cards:card_detail' card.pk %}">
{% card_badge card.card card.quantity %}
</a>
{% endfor %}
</div>
</div>
</div>
</a>
{% else %}
<div class="flex justify-center mt-8">
<div class="flex justify-center mb-1">
<div class="text-sm">
All cards have been accepted.
All cards have been traded.
</div>
</div>
{% endif %}
</div>
<div class="flip-face-footer self-end">
<div class="flex justify-between px-2 pb-2">
<div @click="badgeExpanded = !badgeExpanded" class="flex justify-center h-5 cursor-pointer tooltip tooltip-right" data-tip="Expand/Collapse">
<svg x-bind:class="{ 'rotate-180': badgeExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M19 9l-7 7-7-7" />
</svg>
</div>
<div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer_hash }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-5">
@ -85,7 +70,8 @@
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>
</div>
<div class="cursor-pointer text-gray-500 tooltip tooltip-left" data-tip="Flip to Accepted Trades" @click="flipped = true; acceptanceExpanded = defaultExpanded">
<div class="text-xs">{{ rarity_icon }}</div>
<div class="cursor-pointer text-gray-500 tooltip tooltip-left" data-tip="Flip to Accepted Trades" @click.stop.prevent="flipWithCollapse()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" />
</svg>
@ -96,9 +82,8 @@
<!-- Back Face: Acceptances View -->
<!-- Placed in the same grid cell as the front face -->
<div x-show="flipped" class="flip-face back col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between" style="transform: rotateY(180deg);">
<div x-ref="back" x-show="show_back" x-cloak class="flip-face back col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between" style="transform: rotateY(180deg);">
<div class="self-start">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="relative mt-6 mb-4 mx-2 sm:mx-4">
<div class="grid grid-cols-2">
<span class="text-sm font-semibold text-center">Has</span>
@ -113,7 +98,6 @@
</div>
</div>
</div>
</a>
</div>
<div class="self-start">
<div class="px-2 pb-0">
@ -132,49 +116,47 @@
<span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span>
</div>
</div>'>
<div class="grid grid-cols-2 gap-1 justify-items-center">
<div class="grid grid-cols-2 justify-items-center">
{% card_badge acceptance.requested_card %}
{% card_badge acceptance.offered_card %}
</div>
</a>
{% empty %}
<div class="flex justify-center items-center">
<div class="text-sm">
No trades yet.
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="flex justify-between px-2 pb-2 self-end">
<div class="flex justify-between px-2 pb-2 self-end items-center">
<!-- Back-to-front flip button -->
<div class="text-gray-500 cursor-pointer tooltip tooltip-right" data-tip="Flip to Offered Cards" @click="flipped = false; badgeExpanded = defaultExpanded">
<div class="text-gray-500 cursor-pointer tooltip tooltip-right" data-tip="Flip to Offered Cards" @click.stop.prevent="flipWithCollapse()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061A1.125 1.125 0 0 1 21 8.689v8.122ZM11.25 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061a1.125 1.125 0 0 1 1.683.977v8.122Z" />
</svg>
</div>
<div class="text-xs">{{ rarity_icon }}</div>
<div class="px-1 text-center">
<span class="text-sm font-semibold">
Accepted Trades ({{ acceptances|length }})
<span class="text-xs text-gray-500">
({{ acceptances|length }})
</span>
</div>
<div class="flex justify-center h-5 tooltip tooltip-left" data-tip="Expand/Collapse">
<svg @click="badgeExpanded = !badgeExpanded"
x-bind:class="{ 'rotate-180': badgeExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
/* Ensure proper 3D transformations on the rotating element */
.flip-inner {
transform-style: preserve-3d;
}
/* Hide the back face of each card side */
.flip-face {
.flip-face-back-hidden {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}

View file

@ -45,7 +45,7 @@
<!-- Has Side (inner grid of 2 columns) -->
<div class="grid grid-cols-2 gap-2">
{% for card in have_cards_available %}
{% card_badge card.card card.quantity static=True %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
<!-- Vertical Divider -->
@ -55,7 +55,7 @@
<!-- Wants Side (inner grid of 2 columns) -->
<div class="grid grid-cols-2 gap-2">
{% for card in want_cards_available %}
{% card_badge card.card card.quantity static=True %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
</div>
@ -65,13 +65,17 @@
<!-- Has Side -->
<div class="flex flex-col gap-2">
{% for card in have_cards_available %}
{% card_badge card.card card.quantity static=True %}
{% card_badge card.card card.quantity %}
{% empty %}
<div class="text-xs text-center mb-2 ms-3">None left.</div>
{% endfor %}
</div>
<!-- Wants Side -->
<div class="flex flex-col gap-2">
{% for card in want_cards_available %}
{% card_badge card.card card.quantity static=True %}
{% card_badge card.card card.quantity %}
{% empty %}
<div class="text-xs text-center mb-2{% if expanded %} ms-8{% else %} me-4{% endif %}">None left.</div>
{% endfor %}
</div>
</div>

View file

@ -3,7 +3,7 @@ from django import template
register = template.Library()
@register.inclusion_tag('templatetags/trade_offer.html', takes_context=True)
def render_trade_offer(context, offer, expanded=False):
def render_trade_offer(context, offer):
"""
Renders a trade offer including detailed trade acceptance information.
Freezes the through-model querysets to avoid extra DB hits.
@ -33,7 +33,6 @@ def render_trade_offer(context, offer, expanded=False):
return {
'offer_pk': offer.pk,
'expanded': expanded,
'flipped': flipped, # new flag to control the default face
'offer_hash': offer.hash,
'rarity_icon': offer.rarity_icon,
@ -45,6 +44,7 @@ def render_trade_offer(context, offer, expanded=False):
'in_game_name': offer.initiated_by.in_game_name,
'friend_code': offer.initiated_by.friend_code,
'num_cards_available': len(have_cards_available) + len(want_cards_available),
'request_path': context.get("request").path,
}
@register.inclusion_tag('templatetags/trade_acceptance.html', takes_context=True)
@ -97,8 +97,8 @@ def render_trade_offer_png(context, offer, show_friend_code=False):
]
num_cards = max(len(have_cards_available), len(want_cards_available))
image_height = (num_cards * 52) + ((num_cards - 1) * 8) + 106
# cards | padding between cards | header/footer
image_height = (num_cards * 40) + 106
# cards incl pad + header/footer
if (len(have_cards_available) + len(want_cards_available)) >= 4:
image_width = (4 * 160) + 160
else:

View file

@ -498,9 +498,9 @@ class TradeOfferPNGView(View):
trade_offer = get_object_or_404(TradeOffer, pk=kwargs['pk'])
# If the image is already generated and stored, serve it directly.
if trade_offer.image:
trade_offer.image.open()
return HttpResponse(trade_offer.image.read(), content_type="image/png")
# if trade_offer.image:
# trade_offer.image.open()
# return HttpResponse(trade_offer.image.read(), content_type="image/png")
# Acquire PostgreSQL advisory lock to prevent concurrent generation.
from django.db import connection
@ -509,10 +509,10 @@ class TradeOfferPNGView(View):
cursor.execute("SELECT pg_advisory_lock(%s)", [lock_key])
try:
# Double-check if the image was generated while waiting for the lock.
trade_offer.refresh_from_db()
if trade_offer.image:
trade_offer.image.open()
return HttpResponse(trade_offer.image.read(), content_type="image/png")
# trade_offer.refresh_from_db()
# if trade_offer.image:
# trade_offer.image.open()
# return HttpResponse(trade_offer.image.read(), content_type="image/png")
tag_context = render_trade_offer_png(
{'request': request}, trade_offer, show_friend_code=True