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 = template.Library()
@register.inclusion_tag("templatetags/card_badge.html") @register.inclusion_tag("templatetags/card_badge.html")
def card_badge(card, quantity=None, static=False): def card_badge(card, quantity=None, expanded=False):
return { return {
'quantity': quantity, 'quantity': quantity,
'style': card.style, 'style': card.style,
'name': card.name, 'name': card.name,
'rarity': card.rarity_icon, 'rarity': card.rarity_icon,
'cardset': card.cardset, 'cardset': card.cardset,
'static': static, 'expanded': expanded,
} }
@register.filter @register.filter
@ -26,6 +26,6 @@ def card_badge_inline(card, quantity=None):
'name': card.name, 'name': card.name,
'rarity': card.rarity_icon, 'rarity': card.rarity_icon,
'cardset': card.cardset, 'cardset': card.cardset,
'static': True, 'expanded': True,
}) })
return mark_safe(html) return mark_safe(html)

View file

@ -130,17 +130,55 @@ const $$ = selector => Array.from(document.querySelector(selector));
window.tradeOfferCard = function() { window.tradeOfferCard = function() {
return { return {
flipped: false, flipped: false,
badgeExpanded: false, show_back: false,
acceptanceExpanded: false, collapsed: false,
/** /*
* Update the badge's expanded state. * 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) { flipWithCollapse() {
this.badgeExpanded = expanded; // 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"> <div class="flex justify-center flex-wrap">
{% for card in group.cards %} {% for card in group.cards %}
<a href="{% url 'cards:card_detail' card.pk %}"> <a href="{% url 'cards:card_detail' card.pk %}">
{% card_badge card static=True %} {% card_badge card expanded=True %}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
@ -15,7 +15,7 @@
<div class="flex justify-center flex-wrap"> <div class="flex justify-center flex-wrap">
{% for card in cards %} {% for card in cards %}
<a href="{% url 'cards:card_detail' card.pk %}"> <a href="{% url 'cards:card_detail' card.pk %}">
{% card_badge card static=True %} {% card_badge card expanded=True %}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -4,7 +4,7 @@
{% for card in cards %} {% for card in cards %}
<a href="{% url 'cards:card_detail' card.id %}" <a href="{% url 'cards:card_detail' card.id %}"
class="flex justify-between items-center text-primary no-underline"> 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> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

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

View file

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

View file

@ -1,19 +1,19 @@
{% if not static %} {% if not expanded %}
<div class="relative block"> <div class="relative block m-1">
<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="flex flex-row items-center h-[32px] 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 class="grow 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 class="grow-0 shrink-0 text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</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 class="grow-0 shrink-0 relative w-fit ps-1">
{% 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 %} <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>
</div> </div>
{% else %} {% 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="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-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 align-bottom text-xs">{{ rarity }}</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 align-bottom text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</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 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 %} {% 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>
</div> </div>
{% endif %} {% endif %}

View file

@ -1,104 +1,89 @@
{% load gravatar card_badge cache %} {% load gravatar card_badge cache %}
{% url 'trade_offer_detail' pk=offer_pk as trade_offer_detail_url %}
{% cache 60 trade_offer offer_pk %} {% cache 60 trade_offer offer_pk %}
<div class="trade-offer-card m-2 h-full w-auto flex justify-center"> <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" <div x-data="tradeOfferCard()" x-init="flipped = {{flipped|lower}}; show_back = {{flipped|lower}}" class="trade-offer-card my-auto">
@toggle-all.window="setBadge($event.detail.expanded)"> <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}">
<!-- Flip container providing perspective --> <!-- Front Face: Trade Offer -->
<div class="flip-container" style="perspective: 1000px;"> <!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap -->
<!-- <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">
The rotating element (.flip-inner) now uses CSS Grid to stack its children in a single cell. <!-- Header -->
Persistent border, shadow, and rounding are applied here and the card rotates entirely. <div class="flip-face-header self-start">
--> <!-- Set this container as relative to position the avatar absolutely -->
<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" <div class="relative mt-6 mb-4 mx-2 sm:mx-4">
:class="{'rotate-y-180': flipped}"> <!-- Two-column grid for the labels -->
<div class="grid grid-cols-2">
<!-- Front Face: Trade Offer --> <span class="text-sm font-semibold text-center">Has</span>
<!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap --> <span class="text-sm font-semibold text-center">Wants</span>
<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>
<!-- Header --> <!-- The avatar is placed absolutely and centered -->
<div class="flip-face-header self-start"> <div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block"> <div class="avatar">
<!-- Set this container as relative to position the avatar absolutely --> <div class="w-10 rounded-full">
<div class="relative mt-6 mb-4 mx-2 sm:mx-4"> {{ initiated_by_email|gravatar:40 }}
<!-- Two-column grid for the labels --> </div>
<div class="grid grid-cols-2">
<span class="text-sm font-semibold text-center">Has</span>
<span class="text-sm font-semibold text-center">Wants</span>
</div>
<!-- The avatar is placed absolutely and centered -->
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
<div class="avatar">
<div class="w-10 rounded-full">
{{ initiated_by_email|gravatar:40 }}
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Main Trade Offer Row -->
<div class="flip-face-body self-start">
{% if not flipped %}
<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">
{% 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">
{% 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> </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">
{% for card in have_cards_available %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
<!-- Wants Side -->
<div class="flex flex-col gap-y-2">
{% for card in want_cards_available %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
</div>
</div>
</a>
{% else %} {% else %}
<div class="flex justify-center mt-8"> <div class="flex justify-center mb-1">
<div class="text-sm"> <div class="text-sm">
All cards have been accepted. All cards have been traded.
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="flip-face-footer self-end"> <div class="flip-face-footer self-end">
<div class="flex justify-between px-2 pb-2"> <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"> <div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer_hash }}">
<svg x-bind:class="{ 'rotate-180': badgeExpanded }" <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
class="transition-transform duration-500 h-5 w-5 cursor-pointer" stroke="currentColor" class="size-5">
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" 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" />
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">
<path stroke-linecap="round" stroke-linejoin="round"
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">
<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> </svg>
</div>
<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>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Back Face: Acceptances View --> <!-- Back Face: Acceptances View -->
<!-- Placed in the same grid cell as the front face --> <!-- 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"> <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="relative mt-6 mb-4 mx-2 sm:mx-4">
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<span class="text-sm font-semibold text-center">Has</span> <span class="text-sm font-semibold text-center">Has</span>
@ -113,68 +98,65 @@
</div> </div>
</div> </div>
</div> </div>
</a> </div>
</div> <div class="self-start">
<div class="self-start"> <div class="px-2 pb-0">
<div class="px-2 pb-0"> <div class="space-y-3">
<div class="space-y-3"> {% for acceptance in acceptances %}
{% for acceptance in acceptances %} <a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline block mb-2"
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline block mb-2" data-tooltip-html='<div class="flex items-center space-x-2">
data-tooltip-html='<div class="flex items-center space-x-2"> <div class="avatar">
<div class="avatar"> <div class="w-10 rounded-full">
<div class="w-10 rounded-full"> {{ acceptance.accepted_by.user.email|gravatar:"40" }}
{{ acceptance.accepted_by.user.email|gravatar:"40" }} </div>
</div> </div>
</div> <div class="flex flex-col">
<div class="flex flex-col"> <span class="text-sm">Accepted by: {{ acceptance.accepted_by.user.username }}</span>
<span class="text-sm">Accepted by: {{ acceptance.accepted_by.user.username }}</span> <span class="text-sm">State: {{ acceptance.state }}</span>
<span class="text-sm">State: {{ acceptance.state }}</span> <span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span>
<span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span> </div>
</div> </div>'>
</div>'> <div class="grid grid-cols-2 justify-items-center">
<div class="grid grid-cols-2 gap-1 justify-items-center">
{% card_badge acceptance.requested_card %} {% card_badge acceptance.requested_card %}
{% card_badge acceptance.offered_card %} {% card_badge acceptance.offered_card %}
</div> </div>
</a> </a>
{% endfor %} {% empty %}
<div class="flex justify-center items-center">
<div class="text-sm">
No trades yet.
</div>
</div>
{% endfor %}
</div>
</div> </div>
</div> </div>
</div> <div class="flex justify-between px-2 pb-2 self-end items-center">
<div class="flex justify-between px-2 pb-2 self-end"> <!-- Back-to-front flip button -->
<!-- Back-to-front flip button --> <div class="text-gray-500 cursor-pointer tooltip tooltip-right" data-tip="Flip to Offered Cards" @click.stop.prevent="flipWithCollapse()">
<div class="text-gray-500 cursor-pointer tooltip tooltip-right" data-tip="Flip to Offered Cards" @click="flipped = false; badgeExpanded = defaultExpanded"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<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" />
<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>
</svg> </div>
</div> <div class="text-xs">{{ rarity_icon }}</div>
<div class="px-1 text-center"> <div class="px-1 text-center">
<span class="text-sm font-semibold"> <span class="text-xs text-gray-500">
Accepted Trades ({{ acceptances|length }}) ({{ acceptances|length }})
</span> </span>
</div> </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> </div>
</div> </div>
</div> </div>
</div>
<style> <style>
/* Ensure proper 3D transformations on the rotating element */ /* Ensure proper 3D transformations on the rotating element */
.flip-inner { .flip-inner {
transform-style: preserve-3d; transform-style: preserve-3d;
} }
/* Hide the back face of each card side */ /* Hide the back face of each card side */
.flip-face { .flip-face-back-hidden {
backface-visibility: hidden; backface-visibility: hidden;
-webkit-backface-visibility: hidden; -webkit-backface-visibility: hidden;
} }

View file

@ -45,7 +45,7 @@
<!-- Has Side (inner grid of 2 columns) --> <!-- Has Side (inner grid of 2 columns) -->
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
{% for card in have_cards_available %} {% for card in have_cards_available %}
{% card_badge card.card card.quantity static=True %} {% card_badge card.card card.quantity %}
{% endfor %} {% endfor %}
</div> </div>
<!-- Vertical Divider --> <!-- Vertical Divider -->
@ -55,7 +55,7 @@
<!-- Wants Side (inner grid of 2 columns) --> <!-- Wants Side (inner grid of 2 columns) -->
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
{% for card in want_cards_available %} {% for card in want_cards_available %}
{% card_badge card.card card.quantity static=True %} {% card_badge card.card card.quantity %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -65,13 +65,17 @@
<!-- Has Side --> <!-- Has Side -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{% for card in have_cards_available %} {% 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 %} {% endfor %}
</div> </div>
<!-- Wants Side --> <!-- Wants Side -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{% for card in want_cards_available %} {% 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 %} {% endfor %}
</div> </div>
</div> </div>

View file

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

View file

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