feat(sidecar): per-element BrickData set-detail toggles + price basis
This commit is contained in:
+23
@@ -511,3 +511,26 @@
|
||||
# Use the same badge keys as BK_BADGE_ORDER_GRID
|
||||
# Default: theme,tag,year,parts,instance_count,total_minifigures,total_missing,total_damaged,owner,storage,purchase_date,purchase_location,purchase_price,instructions,rebrickable,bricklink
|
||||
# BK_BADGE_ORDER_DETAIL=theme,tag,year,parts,owner,storage,purchase_date,rebrickable,bricklink
|
||||
|
||||
# Optional: Show/hide individual BrickData (sidecar) elements on the set detail page
|
||||
# All default to true (shown). Badges are controlled separately via BK_BADGE_ORDER_DETAIL.
|
||||
# These take effect live (no restart). Set to false to hide.
|
||||
# BK_SIDECAR_SHOW_DESIGNER: the set designer line
|
||||
# BK_SIDECAR_SHOW_DESCRIPTION: the "About this set" description
|
||||
# BK_SIDECAR_SHOW_NOTES: the Brickset notes
|
||||
# BK_SIDECAR_SHOW_PRICE_PAID: price comparison "Paid" row (and "Change vs paid")
|
||||
# BK_SIDECAR_SHOW_PRICE_MSRP: price comparison retail/MSRP rows (and "Saved vs retail")
|
||||
# BK_SIDECAR_SHOW_PRICE_MARKET: price comparison "Worth now" market value rows
|
||||
# Default: true
|
||||
# BK_SIDECAR_SHOW_DESIGNER=false
|
||||
# BK_SIDECAR_SHOW_DESCRIPTION=false
|
||||
# BK_SIDECAR_SHOW_NOTES=false
|
||||
# BK_SIDECAR_SHOW_PRICE_PAID=false
|
||||
# BK_SIDECAR_SHOW_PRICE_MSRP=false
|
||||
# BK_SIDECAR_SHOW_PRICE_MARKET=false
|
||||
|
||||
# Optional: which BrickLink market value the set-detail "Change vs paid" compares
|
||||
# against: used or new (falls back to the other when one is missing). The statistics
|
||||
# page shows both regardless of this setting. Live-changeable.
|
||||
# Default: used
|
||||
# BK_SIDECAR_PRICE_BASIS=new
|
||||
|
||||
@@ -124,4 +124,16 @@ CONFIG: Final[list[dict[str, Any]]] = [
|
||||
# sidecar use its default. Sent as a query parameter only when set; sidecars
|
||||
# that do not support it simply ignore it.
|
||||
{'n': 'SIDECAR_CURRENCY', 'd': ''},
|
||||
# Per-element show/hide toggles for the BrickData enrichment on the set
|
||||
# detail page. All default on so behaviour is unchanged; badges are handled
|
||||
# separately via BADGE_ORDER_DETAIL.
|
||||
{'n': 'SIDECAR_SHOW_DESIGNER', 'd': True, 'c': bool},
|
||||
{'n': 'SIDECAR_SHOW_DESCRIPTION', 'd': True, 'c': bool},
|
||||
{'n': 'SIDECAR_SHOW_NOTES', 'd': True, 'c': bool},
|
||||
{'n': 'SIDECAR_SHOW_PRICE_PAID', 'd': True, 'c': bool},
|
||||
{'n': 'SIDECAR_SHOW_PRICE_MSRP', 'd': True, 'c': bool},
|
||||
{'n': 'SIDECAR_SHOW_PRICE_MARKET', 'd': True, 'c': bool},
|
||||
# Which BrickLink market value the set-detail "Change vs paid" compares
|
||||
# against: 'used' or 'new'. Statistics shows both regardless.
|
||||
{'n': 'SIDECAR_PRICE_BASIS', 'd': 'used'},
|
||||
]
|
||||
|
||||
@@ -93,7 +93,15 @@ LIVE_CHANGEABLE_VARS: Final[List[str]] = [
|
||||
'BK_SIDECAR_DEFAULT_COVER',
|
||||
'BK_SIDECAR_RETAIL_REGION',
|
||||
'BK_SIDECAR_AUTO_FETCH_PRICE',
|
||||
'BK_SIDECAR_CURRENCY'
|
||||
'BK_SIDECAR_CURRENCY',
|
||||
# Per-element show/hide toggles for the BrickData set-detail enrichment
|
||||
'BK_SIDECAR_SHOW_DESIGNER',
|
||||
'BK_SIDECAR_SHOW_DESCRIPTION',
|
||||
'BK_SIDECAR_SHOW_NOTES',
|
||||
'BK_SIDECAR_SHOW_PRICE_PAID',
|
||||
'BK_SIDECAR_SHOW_PRICE_MSRP',
|
||||
'BK_SIDECAR_SHOW_PRICE_MARKET',
|
||||
'BK_SIDECAR_PRICE_BASIS'
|
||||
]
|
||||
|
||||
# Environment variables that require restart
|
||||
@@ -354,6 +362,13 @@ class ConfigManager:
|
||||
'BK_SIDECAR_DEFAULT_COVER': 'Default cover image source for bulk add: rebrickable, box, or set',
|
||||
'BK_SIDECAR_RETAIL_REGION': 'LEGO.com retail price region for MSRP: US, UK, CA, or DE',
|
||||
'BK_SIDECAR_AUTO_FETCH_PRICE': 'Automatically fetch BrickLink market value when viewing a set (respects the cache TTL), so you do not have to press "Fetch value". Off by default as it is the slower live path.',
|
||||
'BK_SIDECAR_CURRENCY': 'Currency to request BrickLink market values in, e.g. EUR. Empty uses the sidecar default. (The sidecar must support the currency query parameter.)'
|
||||
'BK_SIDECAR_CURRENCY': 'Currency to request BrickLink market values in, e.g. EUR. Empty uses the sidecar default. (The sidecar must support the currency query parameter.)',
|
||||
'BK_SIDECAR_SHOW_DESIGNER': 'Show the BrickData set designer on the set detail page',
|
||||
'BK_SIDECAR_SHOW_DESCRIPTION': 'Show the BrickData "About this set" description on the set detail page',
|
||||
'BK_SIDECAR_SHOW_NOTES': 'Show the BrickData Brickset notes on the set detail page',
|
||||
'BK_SIDECAR_SHOW_PRICE_PAID': 'Show the "Paid" row (and "Change vs paid") in the BrickData price comparison',
|
||||
'BK_SIDECAR_SHOW_PRICE_MSRP': 'Show the retail/MSRP rows (and "Saved vs retail") in the BrickData price comparison',
|
||||
'BK_SIDECAR_SHOW_PRICE_MARKET': 'Show the "Worth now" market value rows in the BrickData price comparison',
|
||||
'BK_SIDECAR_PRICE_BASIS': 'Which BrickLink market value the set-detail "Change vs paid" compares against: used or new (falls back to the other when one is missing). Statistics shows both regardless.'
|
||||
}
|
||||
return help_text.get(var_name, 'No help available for this variable')
|
||||
@@ -114,9 +114,15 @@ def summarize(
|
||||
if msrp is not None and paid is not None:
|
||||
prices['savings_vs_msrp'] = round(msrp - paid, 2)
|
||||
|
||||
# Value movement vs what was paid (using the sealed/new market average,
|
||||
# falling back to used).
|
||||
market_ref = market_new if market_new is not None else market_used
|
||||
# Value movement vs what was paid. SIDECAR_PRICE_BASIS picks which market
|
||||
# average to compare against ('used' or 'new'); fall back to the other when
|
||||
# the preferred one is missing.
|
||||
basis = str(current_app.config.get('SIDECAR_PRICE_BASIS', 'used')).lower()
|
||||
if basis == 'new':
|
||||
market_ref = market_new if market_new is not None else market_used
|
||||
else:
|
||||
market_ref = market_used if market_used is not None else market_new
|
||||
prices['market_basis'] = basis
|
||||
if market_ref is not None and paid is not None:
|
||||
prices['gain_vs_paid'] = round(market_ref - paid, 2)
|
||||
|
||||
|
||||
@@ -554,13 +554,6 @@
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_SIDECAR_TIMEOUT" data-var="BK_SIDECAR_TIMEOUT" min="1" max="120">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="BK_SIDECAR_PRICE_CACHE_HOURS" class="form-label">
|
||||
BK_SIDECAR_PRICE_CACHE_HOURS {{ config_badges('BK_SIDECAR_PRICE_CACHE_HOURS') }}
|
||||
<div class="text-muted small">Hours to cache BrickLink market prices before refetching</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_SIDECAR_PRICE_CACHE_HOURS" data-var="BK_SIDECAR_PRICE_CACHE_HOURS" min="0" max="8760">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="BK_SIDECAR_RETAIL_REGION" class="form-label">
|
||||
BK_SIDECAR_RETAIL_REGION {{ config_badges('BK_SIDECAR_RETAIL_REGION') }}
|
||||
@@ -582,6 +575,13 @@
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_SIDECAR_DEFAULT_COVER" data-var="BK_SIDECAR_DEFAULT_COVER" {{ is_locked('BK_SIDECAR_DEFAULT_COVER') }}>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="BK_SIDECAR_PRICE_BASIS" class="form-label">
|
||||
BK_SIDECAR_PRICE_BASIS {{ config_badges('BK_SIDECAR_PRICE_BASIS') }}
|
||||
<div class="text-muted small">Market value the set-detail "Change vs paid" uses: used or new</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_SIDECAR_PRICE_BASIS" data-var="BK_SIDECAR_PRICE_BASIS" {{ is_locked('BK_SIDECAR_PRICE_BASIS') }}>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SIDECAR_AUTO_FETCH_PRICE" data-var="BK_SIDECAR_AUTO_FETCH_PRICE" {{ is_locked('BK_SIDECAR_AUTO_FETCH_PRICE') }}>
|
||||
@@ -593,6 +593,65 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold text-secondary mb-3 mt-3">Set detail display</h6>
|
||||
<p class="text-muted small">Show or hide individual BrickData elements on the set detail page. Badges are controlled separately via BK_BADGE_ORDER_DETAIL.</p>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 d-flex align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SIDECAR_SHOW_DESIGNER" data-var="BK_SIDECAR_SHOW_DESIGNER" {{ is_locked('BK_SIDECAR_SHOW_DESIGNER') }}>
|
||||
<label class="form-check-label" for="BK_SIDECAR_SHOW_DESIGNER">
|
||||
BK_SIDECAR_SHOW_DESIGNER {{ config_badges('BK_SIDECAR_SHOW_DESIGNER') }}
|
||||
<div class="text-muted small">Show the set designer</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SIDECAR_SHOW_DESCRIPTION" data-var="BK_SIDECAR_SHOW_DESCRIPTION" {{ is_locked('BK_SIDECAR_SHOW_DESCRIPTION') }}>
|
||||
<label class="form-check-label" for="BK_SIDECAR_SHOW_DESCRIPTION">
|
||||
BK_SIDECAR_SHOW_DESCRIPTION {{ config_badges('BK_SIDECAR_SHOW_DESCRIPTION') }}
|
||||
<div class="text-muted small">Show the "About this set" description</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SIDECAR_SHOW_NOTES" data-var="BK_SIDECAR_SHOW_NOTES" {{ is_locked('BK_SIDECAR_SHOW_NOTES') }}>
|
||||
<label class="form-check-label" for="BK_SIDECAR_SHOW_NOTES">
|
||||
BK_SIDECAR_SHOW_NOTES {{ config_badges('BK_SIDECAR_SHOW_NOTES') }}
|
||||
<div class="text-muted small">Show the Brickset notes</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SIDECAR_SHOW_PRICE_PAID" data-var="BK_SIDECAR_SHOW_PRICE_PAID" {{ is_locked('BK_SIDECAR_SHOW_PRICE_PAID') }}>
|
||||
<label class="form-check-label" for="BK_SIDECAR_SHOW_PRICE_PAID">
|
||||
BK_SIDECAR_SHOW_PRICE_PAID {{ config_badges('BK_SIDECAR_SHOW_PRICE_PAID') }}
|
||||
<div class="text-muted small">Price comparison: "Paid" row (and "Change vs paid")</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SIDECAR_SHOW_PRICE_MSRP" data-var="BK_SIDECAR_SHOW_PRICE_MSRP" {{ is_locked('BK_SIDECAR_SHOW_PRICE_MSRP') }}>
|
||||
<label class="form-check-label" for="BK_SIDECAR_SHOW_PRICE_MSRP">
|
||||
BK_SIDECAR_SHOW_PRICE_MSRP {{ config_badges('BK_SIDECAR_SHOW_PRICE_MSRP') }}
|
||||
<div class="text-muted small">Price comparison: retail/MSRP rows (and "Saved vs retail")</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SIDECAR_SHOW_PRICE_MARKET" data-var="BK_SIDECAR_SHOW_PRICE_MARKET" {{ is_locked('BK_SIDECAR_SHOW_PRICE_MARKET') }}>
|
||||
<label class="form-check-label" for="BK_SIDECAR_SHOW_PRICE_MARKET">
|
||||
BK_SIDECAR_SHOW_PRICE_MARKET {{ config_badges('BK_SIDECAR_SHOW_PRICE_MARKET') }}
|
||||
<div class="text-muted small">Price comparison: "Worth now" market value rows</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Settings -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3 mt-4">Advanced Settings</h6>
|
||||
|
||||
|
||||
@@ -8,23 +8,30 @@
|
||||
by macro/badge.html via BADGE_ORDER_DETAIL, so they are not repeated here. #}
|
||||
|
||||
{# --- Designer (web-scraped; the API does not expose it) --- #}
|
||||
{% if s.designer %}
|
||||
{% if s.designer and config['SIDECAR_SHOW_DESIGNER'] %}
|
||||
<div class="card-body border-bottom-0 pt-0">
|
||||
<div class="small text-muted"><i class="ri-pencil-ruler-2-line me-1"></i>Designer: {{ s.designer }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# --- Price comparison: paid / retail / worth now --- #}
|
||||
{% if p.paid is not none or p.msrp is not none or p.msrp_inflated is not none or p.has_market %}
|
||||
{# --- Price comparison: paid / retail / worth now. Each row group has its own
|
||||
live toggle; the whole card hides when all three are off. --- #}
|
||||
{% set show_paid = config['SIDECAR_SHOW_PRICE_PAID'] %}
|
||||
{% set show_msrp = config['SIDECAR_SHOW_PRICE_MSRP'] %}
|
||||
{% set show_market = config['SIDECAR_SHOW_PRICE_MARKET'] %}
|
||||
{% if (show_paid or show_msrp or show_market) and (p.paid is not none or p.msrp is not none or p.msrp_inflated is not none or p.has_market) %}
|
||||
<div class="card-body border-bottom-0 pt-0">
|
||||
<div class="border rounded p-2">
|
||||
<div class="fw-bold small mb-1"><i class="ri-scales-line me-1"></i>Price comparison</div>
|
||||
<table class="table table-sm mb-1 small">
|
||||
<tbody>
|
||||
{% if show_paid %}
|
||||
<tr>
|
||||
<td>Paid</td>
|
||||
<td class="text-end">{% if p.paid is not none %}{{ '%.2f' | format(p.paid) }} {{ config['PURCHASE_CURRENCY'] }}{% else %}<span class="text-muted">—</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if show_msrp %}
|
||||
<tr>
|
||||
<td>Retail (MSRP)</td>
|
||||
<td class="text-end">{% if p.msrp is not none %}{{ '%.2f' | format(p.msrp) }} {{ p.msrp_currency }}{% else %}<span class="text-muted">—</span>{% endif %}</td>
|
||||
@@ -35,6 +42,8 @@
|
||||
<td class="text-end text-muted">{{ '%.2f' | format(p.msrp_inflated) }} {{ p.msrp_inflated_currency }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if show_market %}
|
||||
<tr>
|
||||
<td>Worth now (new)</td>
|
||||
<td class="text-end">{% if p.market_new is not none %}{{ '%.2f' | format(p.market_new) }} {{ p.market_currency }}{% else %}<span class="text-muted">—</span>{% endif %}</td>
|
||||
@@ -43,15 +52,16 @@
|
||||
<td>Worth now (used)</td>
|
||||
<td class="text-end">{% if p.market_used is not none %}{{ '%.2f' | format(p.market_used) }} {{ p.market_currency }}{% else %}<span class="text-muted">—</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% if p.savings_vs_msrp is defined %}
|
||||
{% endif %}
|
||||
{% if show_msrp and p.savings_vs_msrp is defined %}
|
||||
<tr class="border-top">
|
||||
<td>Saved vs retail</td>
|
||||
<td class="text-end {% if p.savings_vs_msrp >= 0 %}text-success{% else %}text-danger{% endif %}">{{ '%+.2f' | format(p.savings_vs_msrp) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if p.gain_vs_paid is defined %}
|
||||
{% if show_paid and p.gain_vs_paid is defined %}
|
||||
<tr>
|
||||
<td>Change vs paid</td>
|
||||
<td>Change vs paid <span class="text-muted">({{ p.market_basis or 'used' }})</span></td>
|
||||
<td class="text-end {% if p.gain_vs_paid >= 0 %}text-success{% else %}text-danger{% endif %}">{{ '%+.2f' | format(p.gain_vs_paid) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -73,7 +83,7 @@
|
||||
|
||||
{# --- Sidecar description, styled like notes. This is the official set blurb,
|
||||
distinct from the user's own notes, so it is always shown when present. --- #}
|
||||
{% if s.description %}
|
||||
{% if s.description and config['SIDECAR_SHOW_DESCRIPTION'] %}
|
||||
<div class="card-body border-bottom-0 pt-0">
|
||||
<div class="alert alert-secondary mb-0" role="alert">
|
||||
<div class="fw-bold small mb-1"><i class="ri-information-line me-1"></i>About this set</div>
|
||||
@@ -83,7 +93,7 @@
|
||||
{% endif %}
|
||||
|
||||
{# --- Brickset Notes (web-scraped; trivia / variants / packaging quirks). --- #}
|
||||
{% if s.notes %}
|
||||
{% if s.notes and config['SIDECAR_SHOW_NOTES'] %}
|
||||
<div class="card-body border-bottom-0 pt-0">
|
||||
<div class="alert alert-light border mb-0" role="alert">
|
||||
<div class="fw-bold small mb-1"><i class="ri-sticky-note-line me-1"></i>Notes</div>
|
||||
|
||||
Reference in New Issue
Block a user