285 lines
12 KiB
HTML
285 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ profile_user.username }} {{ 'showcase.title'|t(lang) }}{% endblock %}
|
|
{% block nav %}{% endblock %}
|
|
{% block content %}
|
|
<div class="showcase-lang-bar">
|
|
<div class="lang-switcher" id="langSwitcher">
|
|
<button type="button" class="lang-globe" onclick="document.getElementById('langDropdown').classList.toggle('show')">🌐</button>
|
|
<div class="lang-dropdown" id="langDropdown">
|
|
{% for code, name in available_languages.items() %}
|
|
<a href="?lang={{ code }}" class="lang-option {% if lang == code %}active{% endif %}">{{ name }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="section-header">
|
|
<h1>📮 {{ profile_user.username }} {{ 'showcase.title'|t(lang) }}</h1>
|
|
</div>
|
|
|
|
{% set sent_visible = [] %}
|
|
{% set received_visible = [] %}
|
|
{% for pc in sent_all %}
|
|
{% if pc.image_front %}
|
|
{% if sent_visible.append(pc) %}{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% for pc in received_all %}
|
|
{% if pc.image_front %}
|
|
{% if received_visible.append(pc) %}{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% set has_content = (sent_visible|length + received_visible|length) > 0 %}
|
|
{% if not has_content %}
|
|
<div class="empty-state">
|
|
<p>{{ 'showcase.empty'|t(lang) }}</p>
|
|
</div>
|
|
{% else %}
|
|
|
|
<div class="tabs" id="tabs">
|
|
<button class="tab active" onclick="switchTab('sent')">{{ 'showcase.tab_sent'|t(lang) }} ({{ sent_visible|length }})</button>
|
|
<button class="tab" onclick="switchTab('received')">{{ 'showcase.tab_received'|t(lang) }} ({{ received_visible|length }})</button>
|
|
</div>
|
|
|
|
<div id="tab-sent" class="tab-panel">
|
|
{% if mode == 'flat' %}
|
|
<div class="showcase-grid" id="grid-sent">
|
|
{% for pc in sent_visible %}
|
|
<div class="showcase-card" data-front="{{ pc.image_front }}" onclick="openLightbox(this)">
|
|
<img src="{{ pc.image_front }}" alt="" class="showcase-card-img" loading="lazy">
|
|
<div class="showcase-card-body">
|
|
<div class="showcase-card-meta">
|
|
{% if pc.country_to_from and pc.country_to %}<span>{{ pc.country_to_from|flag }} {{ pc.country_to_from }} → {{ pc.country_to|flag }} {{ pc.country_to }}</span>
|
|
{% elif pc.country_to %}<span>{{ pc.country_to|flag }}</span>
|
|
{% endif %}
|
|
{% if pc.send_time %}<span>{{ pc.send_time.strftime('%Y-%m-%d') }}</span>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
{% for g in profile_groups %}
|
|
{% set g_sent = [] %}
|
|
{% for pc in g.sent %}
|
|
{% if pc.image_front %}
|
|
{% if g_sent.append(pc) %}{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if g_sent %}
|
|
<h3 style="margin:1rem 0 .75rem;font-size:.95rem;color:var(--text-muted)">{{ g.profile.nickname }}</h3>
|
|
<div class="showcase-grid">
|
|
{% for pc in g_sent %}
|
|
<div class="showcase-card" data-front="{{ pc.image_front }}" onclick="openLightbox(this)">
|
|
<img src="{{ pc.image_front }}" alt="" class="showcase-card-img" loading="lazy">
|
|
<div class="showcase-card-body">
|
|
<div class="showcase-card-meta">
|
|
{% if pc.country_to_from and pc.country_to %}<span>{{ pc.country_to_from|flag }} {{ pc.country_to_from }} → {{ pc.country_to|flag }} {{ pc.country_to }}</span>
|
|
{% elif pc.country_to %}<span>{{ pc.country_to|flag }}</span>
|
|
{% endif %}
|
|
{% if pc.send_time %}<span>{{ pc.send_time.strftime('%Y-%m-%d') }}</span>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
<div id="scroll-sentinel-sent" style="height:1px"></div>
|
|
<div id="scroll-loading-sent" style="text-align:center;padding:1rem;color:var(--text-muted);display:none">⏳</div>
|
|
</div>
|
|
|
|
<div id="tab-received" class="tab-panel" style="display:none">
|
|
{% if mode == 'flat' %}
|
|
<div class="showcase-grid" id="grid-received">
|
|
{% for pc in received_visible %}
|
|
<div class="showcase-card" data-front="{{ pc.image_front }}" onclick="openLightbox(this)">
|
|
<img src="{{ pc.image_front }}" alt="" class="showcase-card-img" loading="lazy">
|
|
<div class="showcase-card-body">
|
|
<div class="showcase-card-meta">
|
|
{% if pc.country_to_from and pc.country_to %}<span>{{ pc.country_to_from|flag }} {{ pc.country_to_from }} → {{ pc.country_to|flag }} {{ pc.country_to }}</span>
|
|
{% elif pc.country_to %}<span>{{ pc.country_to|flag }}</span>
|
|
{% endif %}
|
|
{% if pc.receive_time %}<span>{{ pc.receive_time.strftime('%Y-%m-%d') }}</span>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
{% for g in profile_groups %}
|
|
{% set g_received = [] %}
|
|
{% for pc in g.received %}
|
|
{% if pc.image_front %}
|
|
{% if g_received.append(pc) %}{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% if g_received %}
|
|
<h3 style="margin:1rem 0 .75rem;font-size:.95rem;color:var(--text-muted)">{{ g.profile.nickname }}</h3>
|
|
<div class="showcase-grid">
|
|
{% for pc in g_received %}
|
|
<div class="showcase-card" data-front="{{ pc.image_front }}" onclick="openLightbox(this)">
|
|
<img src="{{ pc.image_front }}" alt="" class="showcase-card-img" loading="lazy">
|
|
<div class="showcase-card-body">
|
|
<div class="showcase-card-meta">
|
|
{% if pc.country_to_from and pc.country_to %}<span>{{ pc.country_to_from|flag }} {{ pc.country_to_from }} → {{ pc.country_to|flag }} {{ pc.country_to }}</span>
|
|
{% elif pc.country_to %}<span>{{ pc.country_to|flag }}</span>
|
|
{% endif %}
|
|
{% if pc.receive_time %}<span>{{ pc.receive_time.strftime('%Y-%m-%d') }}</span>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
<div id="scroll-sentinel-received" style="height:1px"></div>
|
|
<div id="scroll-loading-received" style="text-align:center;padding:1rem;color:var(--text-muted);display:none">⏳</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div id="lightbox" class="lightbox" onclick="closeLightbox(event)">
|
|
<div class="lightbox-content" onclick="event.stopPropagation()">
|
|
<button class="lightbox-close" onclick="closeLightbox(event)">×</button>
|
|
<button class="lightbox-prev" onclick="navLightbox(-1,event)">‹</button>
|
|
<button class="lightbox-next" onclick="navLightbox(1,event)">›</button>
|
|
<img id="lightbox-img" src="" alt="">
|
|
<div id="lightbox-caption" class="lightbox-caption"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
var _username = '{{ profile_user.username }}';
|
|
var _sentPage = 1, _sentMore = {{ 'true' if sent_visible|length > 19 else 'false' }};
|
|
var _recvPage = 1, _recvMore = {{ 'true' if received_visible|length > 19 else 'false' }};
|
|
var _sentLoading = false, _recvLoading = false;
|
|
|
|
function _flag(code) {
|
|
if (!code || code.length !== 2) return code || '';
|
|
return String.fromCodePoint(0x1F1E6 + code.charCodeAt(0) - 65) + String.fromCodePoint(0x1F1E6 + code.charCodeAt(1) - 65);
|
|
}
|
|
|
|
function _appendCard(c, gridId) {
|
|
var grid = document.getElementById(gridId);
|
|
if (!grid) return;
|
|
var div = document.createElement('div');
|
|
div.className = 'showcase-card';
|
|
div.setAttribute('data-front', c.image_front);
|
|
div.onclick = function() { openLightbox(div); };
|
|
var countryHtml = '';
|
|
if (c.country_from && c.country_to) countryHtml = '<span>' + _flag(c.country_from) + ' ' + c.country_from + ' → ' + _flag(c.country_to) + ' ' + c.country_to + '</span>';
|
|
else if (c.country_to) countryHtml = '<span>' + _flag(c.country_to) + '</span>';
|
|
div.innerHTML = '<img src="' + c.image_front + '" alt="" class="showcase-card-img" loading="lazy">' +
|
|
'<div class="showcase-card-body"><div class="showcase-card-meta">' + countryHtml + '</div></div>';
|
|
grid.appendChild(div);
|
|
}
|
|
|
|
function _loadMoreSent() {
|
|
if (_sentLoading || !_sentMore) return;
|
|
_sentLoading = true;
|
|
document.getElementById('scroll-loading-sent').style.display = '';
|
|
fetch('/api/public/cards/' + _username + '?page=' + (_sentPage + 1) + '&limit=20&tab=sent')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
_sentLoading = false;
|
|
document.getElementById('scroll-loading-sent').style.display = 'none';
|
|
_sentPage = data.page;
|
|
_sentMore = data.has_more;
|
|
data.cards.forEach(function(c) { _appendCard(c, 'grid-sent'); });
|
|
if (!_sentMore) { var s = document.getElementById('scroll-sentinel-sent'); if (s) s.remove(); }
|
|
})
|
|
.catch(function() { _sentLoading = false; });
|
|
}
|
|
|
|
function _loadMoreReceived() {
|
|
if (_recvLoading || !_recvMore) return;
|
|
_recvLoading = true;
|
|
document.getElementById('scroll-loading-received').style.display = '';
|
|
fetch('/api/public/cards/' + _username + '?page=' + (_recvPage + 1) + '&limit=20&tab=received')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
_recvLoading = false;
|
|
document.getElementById('scroll-loading-received').style.display = 'none';
|
|
_recvPage = data.page;
|
|
_recvMore = data.has_more;
|
|
data.cards.forEach(function(c) { _appendCard(c, 'grid-received'); });
|
|
if (!_recvMore) { var s = document.getElementById('scroll-sentinel-received'); if (s) s.remove(); }
|
|
})
|
|
.catch(function() { _recvLoading = false; });
|
|
}
|
|
|
|
var _obsSent = new IntersectionObserver(function(e) { if (e[0].isIntersecting) _loadMoreSent(); }, {rootMargin: '400px'});
|
|
var _obsRecv = new IntersectionObserver(function(e) { if (e[0].isIntersecting) _loadMoreReceived(); }, {rootMargin: '400px'});
|
|
var _sSent = document.getElementById('scroll-sentinel-sent');
|
|
var _sRecv = document.getElementById('scroll-sentinel-received');
|
|
if (_sSent) _obsSent.observe(_sSent);
|
|
if (_sRecv) _obsRecv.observe(_sRecv);
|
|
|
|
function switchTab(name) {
|
|
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
|
|
document.querySelectorAll('.tab-panel').forEach(function(p) { p.style.display = 'none'; });
|
|
event.currentTarget.classList.add('active');
|
|
document.getElementById('tab-' + name).style.display = '';
|
|
}
|
|
|
|
var lightboxItems = [];
|
|
var lightboxIdx = 0;
|
|
|
|
function buildLightboxItems() {
|
|
lightboxItems = [];
|
|
var visible = document.querySelectorAll('.tab-panel:not([style*="display:none"]) .showcase-card[data-front]');
|
|
visible.forEach(function(card) {
|
|
var front = card.getAttribute('data-front');
|
|
if (front) lightboxItems.push({src: front});
|
|
});
|
|
}
|
|
|
|
function openLightbox(el) {
|
|
buildLightboxItems();
|
|
var front = el.getAttribute('data-front');
|
|
if (!front) return;
|
|
for (var i = 0; i < lightboxItems.length; i++) {
|
|
if (lightboxItems[i].src === front) { lightboxIdx = i; break; }
|
|
}
|
|
showLightboxItem();
|
|
document.getElementById('lightbox').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
function showLightboxItem() {
|
|
if (!lightboxItems.length) return;
|
|
var item = lightboxItems[lightboxIdx];
|
|
document.getElementById('lightbox-img').src = item.src;
|
|
document.getElementById('lightbox-caption').textContent = (lightboxIdx+1) + '/' + lightboxItems.length;
|
|
}
|
|
|
|
function closeLightbox(e) {
|
|
if (e) e.stopPropagation();
|
|
document.getElementById('lightbox').classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
}
|
|
|
|
function navLightbox(dir, e) {
|
|
if (e) e.stopPropagation();
|
|
if (!lightboxItems.length) return;
|
|
lightboxIdx = (lightboxIdx + dir + lightboxItems.length) % lightboxItems.length;
|
|
showLightboxItem();
|
|
}
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
var lb = document.getElementById('lightbox');
|
|
if (!lb || !lb.classList.contains('active')) return;
|
|
if (e.key === 'Escape') closeLightbox();
|
|
else if (e.key === 'ArrowLeft') navLightbox(-1);
|
|
else if (e.key === 'ArrowRight') navLightbox(1);
|
|
});
|
|
document.addEventListener('click', function(e) {
|
|
var sw = document.getElementById('langSwitcher');
|
|
var dd = document.getElementById('langDropdown');
|
|
if (sw && dd && !sw.contains(e.target)) dd.classList.remove('show');
|
|
});
|
|
</script>
|
|
{% endblock %}
|