Show 3 previous + current (highlighted) + 21 next items in ring sidebar
Ring sidebar now displays a window around the current position: 3 items behind (dimmed), the current item highlighted in blue with a play marker, and 21 items ahead. Each item shows its signed ring offset (-3 to +21). get_ring_related_products returns dicts with product and offset. Views pass offset through to JS. Server-rendered template handles both formats.
This commit is contained in:
parent
98efbfe566
commit
7f8a5e631a
5 changed files with 82 additions and 23 deletions
|
|
@ -697,38 +697,44 @@ def tokenize_and_stem(text):
|
|||
return {stem_word(w) for w in words}
|
||||
|
||||
|
||||
def get_ring_related_products(product, ring, limit=8):
|
||||
"""Get related products by walking forward from current position in the ring.
|
||||
def get_ring_related_products(product, ring, forward=21, backward=3):
|
||||
"""Get products from the ring: backward items, then forward items.
|
||||
|
||||
Returns a list of Product objects in ring order starting after the current product.
|
||||
Returns a list of dicts: {"product": Product, "offset": int}
|
||||
where offset is negative for previous, positive for next.
|
||||
Falls back to get_related_products if product not in ring.
|
||||
"""
|
||||
product_id_str = str(product.id)
|
||||
if product_id_str not in ring:
|
||||
return get_related_products(product, limit=limit)
|
||||
fallback = get_related_products(product, limit=forward)
|
||||
return [{"product": p, "offset": i + 1} for i, p in enumerate(fallback)]
|
||||
|
||||
idx = ring.index(product_id_str)
|
||||
ring_len = len(ring)
|
||||
|
||||
# Walk forward from current position, wrapping around
|
||||
related_ids = []
|
||||
for i in range(1, ring_len):
|
||||
# Collect IDs with their offsets: negative=previous, positive=next
|
||||
items = []
|
||||
for i in range(backward, 0, -1):
|
||||
prev_idx = (idx - i + ring_len) % ring_len
|
||||
items.append((ring[prev_idx], -i))
|
||||
for i in range(1, min(forward + 1, ring_len)):
|
||||
next_idx = (idx + i) % ring_len
|
||||
related_ids.append(ring[next_idx])
|
||||
if len(related_ids) >= limit:
|
||||
break
|
||||
items.append((ring[next_idx], i))
|
||||
|
||||
if not related_ids:
|
||||
if not items:
|
||||
return []
|
||||
|
||||
# Fetch products and preserve ring order
|
||||
products = get_products_by_ids(product.dbsession, related_ids)
|
||||
all_ids = [pid for pid, _ in items]
|
||||
products = get_products_by_ids(product.dbsession, all_ids)
|
||||
if not products:
|
||||
return []
|
||||
|
||||
# Build lookup by ID string for ordering
|
||||
product_map = {str(p.id): p for p in products}
|
||||
return [product_map[pid] for pid in related_ids if pid in product_map]
|
||||
return [
|
||||
{"product": product_map[pid], "offset": offset}
|
||||
for pid, offset in items
|
||||
if pid in product_map
|
||||
]
|
||||
|
||||
|
||||
def get_related_products(product, limit=8):
|
||||
|
|
|
|||
|
|
@ -2761,6 +2761,21 @@ textarea {
|
|||
.related-content-row-watched {
|
||||
opacity: 0.45;
|
||||
}
|
||||
.related-content-row-current {
|
||||
background-color: var(--blue-color, #5871ad);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.related-content-row-current .related-content-index {
|
||||
color: white;
|
||||
}
|
||||
.related-content-now-playing {
|
||||
font-weight: bold;
|
||||
}
|
||||
.related-content-row-prev {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.ring-progress {
|
||||
font-size: 0.8em;
|
||||
|
|
|
|||
|
|
@ -561,13 +561,28 @@
|
|||
}
|
||||
|
||||
var html = headerHtml;
|
||||
var visibleIndex = 0;
|
||||
var currentTitle = document.title;
|
||||
var insertedCurrent = false;
|
||||
|
||||
related.forEach(function(item) {
|
||||
visibleIndex++;
|
||||
var offset = item.offset || 1;
|
||||
var watched = isWatched(item.id);
|
||||
var dimClass = watched ? ' related-content-row-watched' : '';
|
||||
html += '<div class="related-content-row' + dimClass + '">';
|
||||
html += '<span class="related-content-index">' + visibleIndex + '</span>';
|
||||
var prevClass = offset < 0 ? ' related-content-row-prev' : '';
|
||||
|
||||
// Insert "now playing" row before the first forward item
|
||||
if (offset > 0 && !insertedCurrent) {
|
||||
insertedCurrent = true;
|
||||
html += '<div class="related-content-row related-content-row-current">';
|
||||
html += '<span class="related-content-index">▶</span>';
|
||||
html += '<span class="related-content-item related-content-now-playing">';
|
||||
html += '<span class="related-content-title">' + escapeHtml(currentTitle) + '</span>';
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '<div class="related-content-row' + dimClass + prevClass + '">';
|
||||
html += '<span class="related-content-index">' + offset + '</span>';
|
||||
html += '<a href="' + item.url + '" class="related-content-item" data-watch-id="' + item.id + '">';
|
||||
if (item.thumbnail_url) {
|
||||
html += '<img src="' + item.thumbnail_url + '" />';
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<div class="related-content-header">
|
||||
<h3>Up Next</h3>
|
||||
{% if request.shop.watch_mode_enabled %}
|
||||
<span id="ring-progress" class="ring-progress"></span>
|
||||
<label class="watch-autoplay-label js-only">
|
||||
<span class="autoplay-label-text">Autoplay</span>
|
||||
<input type="checkbox" id="watch-autoplay-toggle" checked />
|
||||
|
|
@ -10,9 +11,24 @@
|
|||
</label>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% for related in related_products %}
|
||||
<div class="related-content-row">
|
||||
<span class="related-content-index">{{ loop.index }}</span>
|
||||
{% for item in related_products %}
|
||||
{% if item is mapping %}
|
||||
{% set related = item.product %}
|
||||
{% set offset = item.offset %}
|
||||
{% else %}
|
||||
{% set related = item %}
|
||||
{% set offset = loop.index %}
|
||||
{% endif %}
|
||||
{% if offset == 1 %}
|
||||
<div class="related-content-row related-content-row-current">
|
||||
<span class="related-content-index">▶</span>
|
||||
<span class="related-content-item related-content-now-playing">
|
||||
<span class="related-content-title">{{ product.title }}</span>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="related-content-row{% if offset < 0 %} related-content-row-prev{% endif %}">
|
||||
<span class="related-content-index">{{ offset }}</span>
|
||||
<a href="{{ related.absolute_url(request) }}" class="related-content-item" data-watch-id="{{ related.id }}">
|
||||
{% if "thumbnail1" in related.extensions %}
|
||||
<img src="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ related.s3_path }}/thumbnail1?ts={{ related.updated_timestamp }}" />
|
||||
|
|
|
|||
|
|
@ -90,7 +90,13 @@ def watch_json(request):
|
|||
related = get_related_products(product)
|
||||
|
||||
related_data = []
|
||||
for r in related:
|
||||
for item in related:
|
||||
if isinstance(item, dict):
|
||||
r = item["product"]
|
||||
offset = item["offset"]
|
||||
else:
|
||||
r = item
|
||||
offset = 1
|
||||
r_ext = r.extensions.get("product", "")
|
||||
r_media = get_media_type(r_ext)
|
||||
r_thumb = None
|
||||
|
|
@ -107,6 +113,7 @@ def watch_json(request):
|
|||
"thumbnail_url": r_thumb,
|
||||
"url": f"/{r_p_or_c}/{r.id}/{r.slug}",
|
||||
"media_type": r_media,
|
||||
"offset": offset,
|
||||
})
|
||||
|
||||
# Check if current user is a shop editor (mod)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue