Moxfield import
This commit is contained in:
+72
-15
@@ -44,6 +44,10 @@ textarea { resize: vertical; }
|
||||
.badge-running { color: var(--yellow); background: #1e1a0e; }
|
||||
.badge-queued { color: var(--blue); background: #101622; }
|
||||
.badge-failed { color: var(--red); background: #1e1010; }
|
||||
.badge-source { color: var(--blue); background: #101622; border: 1px solid #1e2e3a; }
|
||||
.mode-toggle .btn.active { color: var(--green); border-color: #1e3a1e; background: #141e14; }
|
||||
.token-badge { position: absolute; top: 3px; right: 3px; font-size: 9px; letter-spacing: 0.5px; color: var(--blue); background: rgba(0,0,0,0.78); padding: 2px 5px; border-radius: 2px; pointer-events: none; }
|
||||
.edit-mode .token-badge { display: none; }
|
||||
|
||||
/* Main panel */
|
||||
.panel { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0; }
|
||||
@@ -131,10 +135,20 @@ textarea { resize: vertical; }
|
||||
<div class="sidebar">
|
||||
<div class="section">
|
||||
<div class="section-label">New Deck</div>
|
||||
<input id="deckName" placeholder="Deck name" />
|
||||
<label class="token-toggle"><input type="checkbox" id="isTokens" onchange="onTokenToggle()" /> Token bundle (no commander)</label>
|
||||
<div id="commanderWrap"><input id="deckCommander" placeholder="Commander (defaults to first card)" /></div>
|
||||
<textarea id="cardList" placeholder="One per line — card name or Scryfall card URL"></textarea>
|
||||
<div class="row mode-toggle" style="margin-top:0">
|
||||
<button class="btn active" id="modeManualBtn" onclick="setCreateMode('manual')" style="flex:1">Manual</button>
|
||||
<button class="btn" id="modeMoxBtn" onclick="setCreateMode('moxfield')" style="flex:1">Moxfield</button>
|
||||
</div>
|
||||
<div id="manualCreateFields" style="margin-top:7px">
|
||||
<input id="deckName" placeholder="Deck name" />
|
||||
<label class="token-toggle"><input type="checkbox" id="isTokens" onchange="onTokenToggle()" /> Token bundle (no commander)</label>
|
||||
<div id="commanderWrap"><input id="deckCommander" placeholder="Commander (defaults to first card)" /></div>
|
||||
<textarea id="cardList" placeholder="One per line — card name or Scryfall card URL"></textarea>
|
||||
</div>
|
||||
<div id="moxfieldCreateFields" style="display:none; margin-top:7px">
|
||||
<input id="moxUrl" placeholder="Moxfield deck URL or ID" />
|
||||
<label class="token-toggle"><input type="checkbox" id="moxIncludeTokens" /> Pull tokens too (for TTS)</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="btn btn-green" id="submitBtn" onclick="submitDeck()" style="flex:1">Submit</button>
|
||||
<button class="btn" onclick="clearForm()">Clear</button>
|
||||
@@ -151,9 +165,11 @@ textarea { resize: vertical; }
|
||||
<div class="progress-bar"><div class="progress-fill" id="progressFill" style="width:0"></div></div>
|
||||
<div class="deck-header" id="deckHeader" style="display:none">
|
||||
<span class="deck-header-title" id="headerTitle"></span>
|
||||
<span class="badge badge-source" id="sourceBadge" style="display:none">MOX</span>
|
||||
<span class="deck-header-meta" id="headerMeta"></span>
|
||||
<span class="price-tag" id="priceTag"></span>
|
||||
<button class="btn btn-gold" id="editBtn" onclick="enterEditMode()" style="display:none">Edit</button>
|
||||
<button class="btn btn-gold" id="syncBtn" onclick="syncMoxfieldDeck()" style="display:none">Sync</button>
|
||||
<button class="btn btn-green" id="saveBtn" onclick="saveEdit()" style="display:none">Save</button>
|
||||
<button class="btn" id="cancelBtn" onclick="exitEditMode(true)" style="display:none">Cancel</button>
|
||||
<button class="btn" id="sortBtn" onclick="toggleSort()" style="display:none">Sort: Default</button>
|
||||
@@ -261,20 +277,38 @@ async function selectDeck(slug) {
|
||||
|
||||
function updateHeader(d) {
|
||||
document.getElementById("headerTitle").textContent = d.deck_name;
|
||||
document.getElementById("sourceBadge").style.display = d.source === "moxfield" ? "" : "none";
|
||||
const pct = d.total > 0 ? d.done / d.total * 100 : (d.status === "complete" ? 100 : 0);
|
||||
document.getElementById("progressFill").style.width = pct + "%";
|
||||
document.getElementById("headerMeta").textContent = d.total > 0 ? `${d.done}/${d.total}` : "";
|
||||
document.getElementById("priceTag").textContent = d.price_usd > 0 ? `$${d.price_usd.toFixed(2)}` : "";
|
||||
const complete = d.status === "complete";
|
||||
const terminal = complete || d.status === "failed";
|
||||
const isMox = d.source === "moxfield";
|
||||
document.getElementById("dlBtn").style.display = complete && !editMode ? "" : "none";
|
||||
document.getElementById("delBtn").style.display = terminal && !editMode ? "" : "none";
|
||||
document.getElementById("editBtn").style.display = terminal && !editMode ? "" : "none";
|
||||
document.getElementById("editBtn").style.display = terminal && !editMode && !isMox ? "" : "none";
|
||||
document.getElementById("syncBtn").style.display = terminal && !editMode && isMox ? "" : "none";
|
||||
document.getElementById("saveBtn").style.display = editMode ? "" : "none";
|
||||
document.getElementById("cancelBtn").style.display = editMode ? "" : "none";
|
||||
document.getElementById("sortBtn").style.display = !editMode ? "" : "none";
|
||||
}
|
||||
|
||||
async function syncMoxfieldDeck() {
|
||||
if (!activeDeck) return;
|
||||
const btn = document.getElementById("syncBtn");
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await api("POST", `/decks/${activeDeck}/moxfield-sync`);
|
||||
await loadDecks();
|
||||
await selectDeck(activeDeck);
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshCards(slug) {
|
||||
if (activeDeck !== slug) return;
|
||||
try {
|
||||
@@ -310,7 +344,7 @@ function renderCards(slug, cards) {
|
||||
const isRemoved = editRemovals.has(card.id);
|
||||
const label = cardLabel(card);
|
||||
const priceStr = card.price_usd > 0 ? `$${card.price_usd.toFixed(2)}` : null;
|
||||
const tileKey = [card.filename, card.exists, card.back_exists, isCommander, isRemoved, editMode, card.fetch_status, label, (card.price_usd || 0)].join("|");
|
||||
const tileKey = [card.filename, card.exists, card.back_exists, isCommander, isRemoved, editMode, card.fetch_status, label, (card.price_usd || 0), card.is_token].join("|");
|
||||
|
||||
if (!tile) { tile = document.createElement("div"); grid.appendChild(tile); tile.dataset.key = ""; }
|
||||
|
||||
@@ -324,6 +358,7 @@ function renderCards(slug, cards) {
|
||||
<img src="${imgUrl}" loading="lazy" alt="${esc(label)}" onclick="showLightbox('${imgUrl}')">
|
||||
<span class="card-label">${isCommander ? "♛ " : ""}${esc(label)}</span>
|
||||
${isCommander ? `<span class="commander-crown">♛</span>` : ""}
|
||||
${card.is_token ? `<span class="token-badge">TOKEN</span>` : ""}
|
||||
${card.back_exists ? `<div class="dfc-badge" title="flip side" onclick="event.stopPropagation();showLightbox('/cards/${slug}/${esc(card.back_filename)}')">⇄</div>` : ""}
|
||||
${priceStr ? `<span class="card-price">${priceStr}</span>` : `<span class="card-price free">$0</span>`}
|
||||
<button class="card-overlay-btn btn-remove-card" onclick="toggleRemove(${card.id})">✕</button>
|
||||
@@ -561,19 +596,40 @@ function showLightbox(url) {
|
||||
function closeLightbox() { document.getElementById("lightbox").classList.remove("open"); }
|
||||
document.addEventListener("keydown", e => { if (e.key === "Escape") closeLightbox(); });
|
||||
|
||||
let createMode = "manual";
|
||||
|
||||
function setCreateMode(mode) {
|
||||
createMode = mode;
|
||||
document.getElementById("manualCreateFields").style.display = mode === "manual" ? "" : "none";
|
||||
document.getElementById("moxfieldCreateFields").style.display = mode === "moxfield" ? "" : "none";
|
||||
document.getElementById("modeManualBtn").classList.toggle("active", mode === "manual");
|
||||
document.getElementById("modeMoxBtn").classList.toggle("active", mode === "moxfield");
|
||||
document.getElementById("submitError").style.display = "none";
|
||||
}
|
||||
|
||||
async function submitDeck() {
|
||||
const name = document.getElementById("deckName").value.trim();
|
||||
const cards = document.getElementById("cardList").value.trim();
|
||||
const isTokens = document.getElementById("isTokens").checked;
|
||||
const commander = document.getElementById("deckCommander").value.trim();
|
||||
const err = document.getElementById("submitError");
|
||||
err.style.display = "none";
|
||||
if (!name) { err.textContent = "Need a name."; err.style.display = ""; return; }
|
||||
if (!isTokens && !cards) { err.textContent = "Need a card list (or check Token bundle)."; err.style.display = ""; return; }
|
||||
const btn = document.getElementById("submitBtn");
|
||||
btn.disabled = true;
|
||||
|
||||
let r;
|
||||
try {
|
||||
const r = await api("POST", "/decks", { deck_name: name, cards, commander, is_tokens: isTokens });
|
||||
if (createMode === "moxfield") {
|
||||
const url = document.getElementById("moxUrl").value.trim();
|
||||
const includeTokens = document.getElementById("moxIncludeTokens").checked;
|
||||
if (!url) { err.textContent = "Need a Moxfield deck URL."; err.style.display = ""; return; }
|
||||
btn.disabled = true;
|
||||
r = await api("POST", "/decks/moxfield", { url, include_tokens: includeTokens });
|
||||
} else {
|
||||
const name = document.getElementById("deckName").value.trim();
|
||||
const cards = document.getElementById("cardList").value.trim();
|
||||
const isTokens = document.getElementById("isTokens").checked;
|
||||
const commander = document.getElementById("deckCommander").value.trim();
|
||||
if (!name) { err.textContent = "Need a name."; err.style.display = ""; return; }
|
||||
if (!isTokens && !cards) { err.textContent = "Need a card list (or check Token bundle)."; err.style.display = ""; return; }
|
||||
btn.disabled = true;
|
||||
r = await api("POST", "/decks", { deck_name: name, cards, commander, is_tokens: isTokens });
|
||||
}
|
||||
clearForm();
|
||||
await loadDecks();
|
||||
selectDeck(r.slug);
|
||||
@@ -589,8 +645,9 @@ function onTokenToggle() {
|
||||
}
|
||||
|
||||
function clearForm() {
|
||||
["deckName","deckCommander","cardList"].forEach(id => document.getElementById(id).value = "");
|
||||
["deckName","deckCommander","cardList","moxUrl"].forEach(id => document.getElementById(id).value = "");
|
||||
document.getElementById("isTokens").checked = false;
|
||||
document.getElementById("moxIncludeTokens").checked = false;
|
||||
onTokenToggle();
|
||||
document.getElementById("submitError").style.display = "none";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user