diff --git a/client/src/adapter/draft-adapter.ts b/client/src/adapter/draft-adapter.ts index 81900c1a10..3212f21fc6 100644 --- a/client/src/adapter/draft-adapter.ts +++ b/client/src/adapter/draft-adapter.ts @@ -11,6 +11,7 @@ export interface DraftCardInstance { colors: string[]; cmc: number; type_line: string; + is_land: boolean; } // @sync-with: crates/draft-core/src/view.rs diff --git a/client/src/components/draft/LimitedDeckBuilder.tsx b/client/src/components/draft/LimitedDeckBuilder.tsx index 0c61e3cf8f..3768ca7d8d 100644 --- a/client/src/components/draft/LimitedDeckBuilder.tsx +++ b/client/src/components/draft/LimitedDeckBuilder.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { AnimatePresence, motion } from "framer-motion"; @@ -219,6 +219,7 @@ export function LimitedDeckBuilder({ const submitDeck = onSubmitDeck ?? quickSubmitDeck; const [hoveredCard, setHoveredCard] = useState(null); + const [copied, setCopied] = useState(false); const pool = useMemo(() => view?.pool ?? [], [view?.pool]); @@ -232,18 +233,71 @@ export function LimitedDeckBuilder({ [pool, mainDeck], ); - const totalLands = useMemo( + const landNameSet = useMemo( + () => + new Set( + pool + .filter((c) => c.is_land) + .map((c) => c.name), + ), + [pool], + ); + + const mainDeckSpells = useMemo( + () => mainDeck.filter((name) => !landNameSet.has(name)), + [mainDeck, landNameSet], + ); + + const deckLandCount = mainDeck.length - mainDeckSpells.length; + + const basicLands = useMemo( () => Object.values(landCounts).reduce((sum, n) => sum + n, 0), [landCounts], ); - const totalCards = mainDeck.length + totalLands; + const totalCards = mainDeck.length + basicLands; const minDeckSize = view?.min_deck_size ?? 40; const addableCards = view?.addable_cards?.length ? view.addable_cards : BASIC_LANDS.map((land) => land.name); const deckValid = totalCards >= minDeckSize; + const copyDeckList = useCallback(() => { + const toLines = (names: string[], extra: Record = {}): string[] => { + const countMap = new Map(); + for (const name of names) { + countMap.set(name, (countMap.get(name) ?? 0) + 1); + } + for (const [name, count] of Object.entries(extra)) { + if (count > 0) { + countMap.set(name, (countMap.get(name) ?? 0) + count); + } + } + const lines: string[] = []; + for (const [name, count] of countMap) { + lines.push(`${count} ${name}`); + } + return lines; + }; + + const sideboardNames = remainingPool.map((c) => c.name); + const deckLines = toLines(mainDeck, landCounts); + const sideboardLines = toLines(sideboardNames); + + const text = [ + "Deck", + ...deckLines, + "", + "Sideboard", + ...sideboardLines, + ].join("\n"); + + void navigator.clipboard.writeText(text).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 1800); + }); + }, [mainDeck, landCounts, remainingPool]); + if (!view) return null; return ( @@ -254,7 +308,7 @@ export function LimitedDeckBuilder({ mobileLayout="compact" onDismiss={() => setHoveredCard(null)} /> - +
{/* Left column: Pool + Main Deck */} @@ -344,7 +398,7 @@ export function LimitedDeckBuilder({ {/* Mana curve */}
- +
{/* Actions */} @@ -358,6 +412,17 @@ export function LimitedDeckBuilder({ {t("limitedDeck.suggestDeck")} )} +