mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-17 16:10:12 +01:00
web: DRY Step 5 and alternatives (partial+macro), centralize start_ctx/owned_set, adopt builder_*
This commit is contained in:
parent
fe9aabbce9
commit
014bcc37b7
24 changed files with 1200 additions and 766 deletions
34
code/web/templates/build/_alternatives.html
Normal file
34
code/web/templates/build/_alternatives.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{# Alternatives panel partial.
|
||||
Expects: name (seed display), require_owned (bool), items = [
|
||||
{ 'name': display_name, 'name_lower': lower, 'owned': bool, 'tags': list[str] }
|
||||
]
|
||||
#}
|
||||
<div class="alts" style="margin-top:.35rem; padding:.5rem; border:1px solid var(--border); border-radius:8px; background:#0f1115;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.25rem;">
|
||||
<strong>Alternatives</strong>
|
||||
{% set toggle_q = '0' if require_owned else '1' %}
|
||||
{% set toggle_label = 'Owned only: On' if require_owned else 'Owned only: Off' %}
|
||||
<button class="btn" hx-get="/build/alternatives?name={{ name|urlencode }}&owned_only={{ toggle_q }}"
|
||||
hx-target="closest .alts" hx-swap="outerHTML">{{ toggle_label }}</button>
|
||||
</div>
|
||||
{% if not items or items|length == 0 %}
|
||||
<div class="muted">No alternatives found{{ ' (owned only)' if require_owned else '' }}.</div>
|
||||
{% else %}
|
||||
<ul style="list-style:none; padding:0; margin:0; display:grid; gap:.25rem;">
|
||||
{% for it in items %}
|
||||
{% set badge = '✔' if it.owned else '✖' %}
|
||||
{% set title = 'Owned' if it.owned else 'Not owned' %}
|
||||
{% set tags = (it.tags or []) %}
|
||||
<li>
|
||||
<span class="owned-badge" title="{{ title }}">{{ badge }}</span>
|
||||
<button class="btn" data-card-name="{{ it.name }}"
|
||||
data-tags="{{ tags|join(', ') }}" hx-post="/build/replace"
|
||||
hx-vals='{"old":"{{ name }}", "new":"{{ it.name }}"}'
|
||||
hx-target="closest .alts" hx-swap="outerHTML" title="Lock this alternative and unlock the current pick">
|
||||
Replace with {{ it.name }}
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
21
code/web/templates/build/_setup_prompt_modal.html
Normal file
21
code/web/templates/build/_setup_prompt_modal.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="setupPromptTitle" style="position:fixed; inset:0; z-index:1000; display:flex; align-items:center; justify-content:center;">
|
||||
<div class="modal-backdrop" style="position:absolute; inset:0; background:rgba(0,0,0,.6);"></div>
|
||||
<div class="modal-content" style="position:relative; max-width:560px; width:clamp(320px, 90vw, 560px); background:#0f1115; border:1px solid var(--border); border-radius:10px; box-shadow:0 10px 30px rgba(0,0,0,.5); padding:1rem;">
|
||||
<div class="modal-header">
|
||||
<h3 id="setupPromptTitle">{{ title or 'Setup required' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ message or 'The card database and tags need to be prepared before building a deck.' }}</p>
|
||||
</div>
|
||||
<div class="modal-footer" style="display:flex; gap:.5rem; justify-content:flex-end; margin-top:1rem;">
|
||||
<button type="button" class="btn" onclick="this.closest('.modal').remove()">Cancel</button>
|
||||
<a class="btn-continue" href="{{ action_url }}" hx-boost="true" hx-target="body" hx-swap="innerHTML">{{ action_label or 'Run Setup' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
function onKey(e){ if (e.key === 'Escape'){ e.preventDefault(); try{ var m=document.querySelector('.modal'); if(m){ m.remove(); document.removeEventListener('keydown', onKey); } }catch(_){ } } }
|
||||
document.addEventListener('keydown', onKey);
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -85,6 +85,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% if locked_cards is defined and locked_cards %}
|
||||
{% from 'partials/_macros.html' import lock_button %}
|
||||
<details id="locked-section" style="margin-top:.5rem;">
|
||||
<summary>Locked cards (always kept)</summary>
|
||||
<ul id="locked-list" style="list-style:none; padding:0; margin:.35rem 0 0; display:grid; gap:.35rem;">
|
||||
|
|
@ -93,12 +94,9 @@
|
|||
<span class="chip"><span class="dot"></span> {{ lk.name }}</span>
|
||||
<span class="muted">{% if lk.owned %}✔ Owned{% else %}✖ Not owned{% endif %}</span>
|
||||
{% if lk.in_deck %}<span class="muted">• In deck</span>{% else %}<span class="muted">• Will be included on rerun</span>{% endif %}
|
||||
<form hx-post="/build/lock" hx-target="closest li" hx-swap="outerHTML" onsubmit="try{toast('Unlocked {{ lk.name }}');}catch(_){}" style="display:inline; margin-left:auto;">
|
||||
<input type="hidden" name="name" value="{{ lk.name }}" />
|
||||
<input type="hidden" name="locked" value="0" />
|
||||
<input type="hidden" name="from_list" value="1" />
|
||||
<button type="submit" class="btn" title="Unlock" aria-pressed="true">Unlock</button>
|
||||
</form>
|
||||
<div class="lock-box" style="display:inline; margin-left:auto;">
|
||||
{{ lock_button(lk.name, True, from_list=True, target_selector='closest li') }}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
@ -236,10 +234,9 @@
|
|||
<div class="owned-badge" title="{{ 'Owned' if owned else 'Not owned' }}" aria-label="{{ 'Owned' if owned else 'Not owned' }}">{% if owned %}✔{% else %}✖{% endif %}</div>
|
||||
<div class="name">{{ c.name|safe }}{% if c.count and c.count > 1 %} ×{{ c.count }}{% endif %}</div>
|
||||
<div class="lock-box" id="lock-{{ group_idx }}-{{ loop.index0 }}" style="display:flex; justify-content:center; gap:.25rem; margin-top:.25rem;">
|
||||
<button type="button" class="btn-lock" title="{{ 'Unlock this card (kept across reruns)' if is_locked else 'Lock this card (keep across reruns)' }}" aria-pressed="{{ 'true' if is_locked else 'false' }}"
|
||||
hx-post="/build/lock" hx-target="closest .lock-box" hx-swap="innerHTML"
|
||||
hx-vals='{"name": "{{ c.name }}", "locked": "{{ '0' if is_locked else '1' }}"}'>{{ '🔒 Unlock' if is_locked else '🔓 Lock' }}</button>
|
||||
</div>
|
||||
{% from 'partials/_macros.html' import lock_button %}
|
||||
{{ lock_button(c.name, is_locked) }}
|
||||
</div>
|
||||
{% if c.reason %}
|
||||
<div style="display:flex; justify-content:center; margin-top:.25rem; gap:.35rem; flex-wrap:wrap;">
|
||||
<button type="button" class="btn-why" aria-expanded="false">Why?</button>
|
||||
|
|
@ -274,10 +271,9 @@
|
|||
<div class="owned-badge" title="{{ 'Owned' if owned else 'Not owned' }}" aria-label="{{ 'Owned' if owned else 'Not owned' }}">{% if owned %}✔{% else %}✖{% endif %}</div>
|
||||
<div class="name">{{ c.name|safe }}{% if c.count and c.count > 1 %} ×{{ c.count }}{% endif %}</div>
|
||||
<div class="lock-box" id="lock-{{ loop.index0 }}" style="display:flex; justify-content:center; gap:.25rem; margin-top:.25rem;">
|
||||
<button type="button" class="btn-lock" title="{{ 'Unlock this card (kept across reruns)' if is_locked else 'Lock this card (keep across reruns)' }}" aria-pressed="{{ 'true' if is_locked else 'false' }}"
|
||||
hx-post="/build/lock" hx-target="closest .lock-box" hx-swap="innerHTML"
|
||||
hx-vals='{"name": "{{ c.name }}", "locked": "{{ '0' if is_locked else '1' }}"}'>{{ '🔒 Unlock' if is_locked else '🔓 Lock' }}</button>
|
||||
</div>
|
||||
{% from 'partials/_macros.html' import lock_button %}
|
||||
{{ lock_button(c.name, is_locked) }}
|
||||
</div>
|
||||
{% if c.reason %}
|
||||
<div style="display:flex; justify-content:center; margin-top:.25rem; gap:.35rem; flex-wrap:wrap;">
|
||||
<button type="button" class="btn-why" aria-expanded="false">Why?</button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue