mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2026-03-25 06:26:31 +01:00
feat: revamp multicopy flow with include/exclude conflict dialogs (#60)
Some checks failed
CI / build (push) Has been cancelled
Some checks failed
CI / build (push) Has been cancelled
* feat: revamp multicopy flow with include/exclude conflict dialogs * feat: revamp multicopy flow with include/exclude conflict dialogs
This commit is contained in:
parent
4aa41adb20
commit
1aa8e4d7e8
14 changed files with 665 additions and 252 deletions
|
|
@ -8,9 +8,10 @@ for deck building, including the card toggle endpoint and summary rendering.
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from fastapi import APIRouter, Request, Form
|
||||
from fastapi import APIRouter, Request, Form, Query
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
|
||||
from deck_builder import builder_constants as bc
|
||||
from ..app import ALLOW_MUST_HAVES, templates
|
||||
from ..services.build_utils import step5_base_ctx
|
||||
from ..services.tasks import get_session, new_sid
|
||||
|
|
@ -21,6 +22,15 @@ from .build import _merge_hx_trigger
|
|||
router = APIRouter()
|
||||
|
||||
|
||||
def _is_multi_copy_archetype(card_name: str) -> dict | None:
|
||||
"""Return archetype dict if card_name exactly matches a known multi-copy archetype, else None."""
|
||||
normalized = card_name.strip().lower()
|
||||
for archetype in bc.MULTI_COPY_ARCHETYPES.values():
|
||||
if str(archetype.get("name", "")).strip().lower() == normalized:
|
||||
return archetype
|
||||
return None
|
||||
|
||||
|
||||
def _must_have_state(sess: dict) -> tuple[dict[str, Any], list[str], list[str]]:
|
||||
"""
|
||||
Extract include/exclude card lists and enforcement settings from session.
|
||||
|
|
@ -129,6 +139,22 @@ async def toggle_must_haves(
|
|||
sid = new_sid()
|
||||
sess = get_session(sid)
|
||||
|
||||
# R13: Multi-copy archetype conflict detection
|
||||
if enabled_flag and ALLOW_MUST_HAVES:
|
||||
archetype = _is_multi_copy_archetype(name)
|
||||
if list_key == "include" and archetype:
|
||||
ctx = {"request": request, "archetype": archetype, "card_name": name}
|
||||
resp = templates.TemplateResponse("partials/multicopy_include_dialog.html", ctx)
|
||||
resp.set_cookie("sid", sid, httponly=True, samesite="lax")
|
||||
return resp
|
||||
if list_key == "exclude":
|
||||
active_mc = sess.get("multi_copy") or {}
|
||||
if active_mc and str(active_mc.get("name", "")).strip().lower() == name.lower():
|
||||
ctx = {"request": request, "archetype": active_mc, "card_name": name}
|
||||
resp = templates.TemplateResponse("partials/multicopy_exclude_warning.html", ctx)
|
||||
resp.set_cookie("sid", sid, httponly=True, samesite="lax")
|
||||
return resp
|
||||
|
||||
includes = list(sess.get("include_cards") or [])
|
||||
excludes = list(sess.get("exclude_cards") or [])
|
||||
include_lookup = {str(v).strip().lower(): str(v) for v in includes if str(v).strip()}
|
||||
|
|
@ -214,3 +240,129 @@ async def toggle_must_haves(
|
|||
except Exception:
|
||||
pass
|
||||
return response
|
||||
|
||||
|
||||
@router.get("/must-haves/summary", response_class=HTMLResponse)
|
||||
async def must_haves_summary(request: Request) -> HTMLResponse:
|
||||
"""Return the current include/exclude summary fragment (used by dialog cancel actions)."""
|
||||
sid = request.cookies.get("sid") or new_sid()
|
||||
sess = get_session(sid)
|
||||
return _render_include_exclude_summary(request, sess, sid)
|
||||
|
||||
|
||||
@router.get("/must-haves/multicopy-dialog", response_class=HTMLResponse)
|
||||
async def multicopy_include_dialog_view(
|
||||
request: Request,
|
||||
card_name: str = Query(...),
|
||||
) -> HTMLResponse:
|
||||
"""Return the count-picker dialog fragment for a multi-copy archetype card."""
|
||||
archetype = _is_multi_copy_archetype(card_name)
|
||||
if not archetype:
|
||||
return HTMLResponse("", status_code=200)
|
||||
ctx = {"request": request, "archetype": archetype, "card_name": card_name}
|
||||
return templates.TemplateResponse("partials/multicopy_include_dialog.html", ctx)
|
||||
|
||||
|
||||
@router.get("/must-haves/exclude-archetype-warning", response_class=HTMLResponse)
|
||||
async def exclude_archetype_warning_view(
|
||||
request: Request,
|
||||
card_name: str = Query(...),
|
||||
) -> HTMLResponse:
|
||||
"""Return the warning dialog fragment for excluding the currently-active archetype."""
|
||||
sid = request.cookies.get("sid") or new_sid()
|
||||
sess = get_session(sid)
|
||||
active_mc = sess.get("multi_copy") or {}
|
||||
ctx = {"request": request, "archetype": active_mc, "card_name": card_name}
|
||||
return templates.TemplateResponse("partials/multicopy_exclude_warning.html", ctx)
|
||||
|
||||
|
||||
@router.post("/must-haves/save-archetype-include", response_class=HTMLResponse)
|
||||
async def save_archetype_include(
|
||||
request: Request,
|
||||
card_name: str = Form(...),
|
||||
choice_id: str = Form(...),
|
||||
count: int = Form(None),
|
||||
thrumming: str | None = Form(None),
|
||||
) -> HTMLResponse:
|
||||
"""Save multi-copy archetype selection and add card to the must-include list.
|
||||
|
||||
Combines the archetype save (multicopy/save) and include toggle into a single
|
||||
action, called from the multicopy_include_dialog when the user confirms.
|
||||
"""
|
||||
if not ALLOW_MUST_HAVES:
|
||||
return JSONResponse({"error": "Must-have lists are disabled"}, status_code=403)
|
||||
|
||||
sid = request.cookies.get("sid") or new_sid()
|
||||
sess = get_session(sid)
|
||||
|
||||
# Persist archetype selection (mirrors multicopy/save logic)
|
||||
meta = bc.MULTI_COPY_ARCHETYPES.get(str(choice_id), {})
|
||||
archetype_name = meta.get("name") or card_name.strip()
|
||||
if count is None:
|
||||
count = int(meta.get("default_count", 25))
|
||||
try:
|
||||
count = int(count)
|
||||
except Exception:
|
||||
count = int(meta.get("default_count", 25))
|
||||
printed_cap = meta.get("printed_cap")
|
||||
if isinstance(printed_cap, int) and printed_cap > 0:
|
||||
count = max(1, min(printed_cap, count))
|
||||
sess["multi_copy"] = {
|
||||
"id": str(choice_id),
|
||||
"name": archetype_name,
|
||||
"count": count,
|
||||
"thrumming": str(thrumming or "").strip().lower() in {"1", "true", "on", "yes"},
|
||||
}
|
||||
try:
|
||||
if "mc_applied_key" in sess:
|
||||
del sess["mc_applied_key"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Add card to include list
|
||||
name = card_name.strip()
|
||||
key = name.lower()
|
||||
includes = list(sess.get("include_cards") or [])
|
||||
include_lookup = {str(v).strip().lower(): str(v) for v in includes}
|
||||
if key not in include_lookup:
|
||||
includes.append(name)
|
||||
# Remove from excludes if present
|
||||
excludes = [c for c in (sess.get("exclude_cards") or []) if str(c).strip().lower() != key]
|
||||
sess["include_cards"] = includes
|
||||
sess["exclude_cards"] = excludes
|
||||
|
||||
return _render_include_exclude_summary(request, sess, sid)
|
||||
|
||||
|
||||
@router.post("/must-haves/clear-archetype", response_class=HTMLResponse)
|
||||
async def clear_archetype(
|
||||
request: Request,
|
||||
card_name: str | None = Form(None),
|
||||
) -> HTMLResponse:
|
||||
"""Clear the active multi-copy archetype and optionally add card to the exclude list.
|
||||
|
||||
Called when user confirms the exclude-archetype-warning dialog.
|
||||
"""
|
||||
sid = request.cookies.get("sid") or new_sid()
|
||||
sess = get_session(sid)
|
||||
|
||||
for k in ("multi_copy", "mc_applied_key", "mc_seen_keys"):
|
||||
try:
|
||||
if k in sess:
|
||||
del sess[k]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if card_name:
|
||||
name = str(card_name).strip()
|
||||
key = name.lower()
|
||||
excludes = list(sess.get("exclude_cards") or [])
|
||||
exclude_lookup = {str(v).strip().lower(): str(v) for v in excludes}
|
||||
if key not in exclude_lookup:
|
||||
excludes.append(name)
|
||||
# Remove from includes if present
|
||||
includes = [c for c in (sess.get("include_cards") or []) if str(c).strip().lower() != key]
|
||||
sess["include_cards"] = includes
|
||||
sess["exclude_cards"] = excludes
|
||||
|
||||
return _render_include_exclude_summary(request, sess, sid)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue