@page "/deck-builder" @page "/deck-builder/{Id:guid}" @rendermode InteractiveServer @using Chrono.Model @inject AppDbContext Db @inject NavigationManager Nav @(Id == null ? "New Deck" : "Edit Deck") - Chrono CCG
My Decks @ValidationMessage @if (!string.IsNullOrEmpty(saveMessage)) { @saveMessage }
@if (search.Length > 0) { }
@for (var c = 1; c <= 6; c++) { var cols = c; }
@foreach (var card in BrowserCards) { var count = CountInDeck(card.Name); var maxed = count >= 3;
@card.Name @if (count > 0) { @count } @if (maxed) {
}
@card.Name
}
40 ? "count-over" : "count-under")"> @deckCards.Count/40
@if (deckCards.Count > 0) {
@for (var cost = 0; cost <= 10; cost++) { var c = cost; var costCount = ManaCurveData.TryGetValue(c, out var v) ? v : 0; var maxVal = ManaCurveData.Values.DefaultIfEmpty(1).Max(); var heightPct = maxVal > 0 ? costCount * 100 / maxVal : 0;
@(costCount > 0 ? costCount.ToString() : "")
@(c == 10 ? "10+" : c.ToString())
}
}
@if (DeckGroups.Count == 0) {
Click cards on the left to add them
} @foreach (var group in DeckGroups) { var cardData = group.Card;
@cardData.Name @cardData.Name @if (cardData.Cost.HasValue) { @cardData.Cost }
@group.Count
}
Divers @divers.Count(d => d != null)/2
@for (var i = 0; i < 2; i++) { var idx = i; var diver = idx < divers.Count ? divers[idx] : null;
@if (diver != null) { var diverCard = AllCards.FirstOrDefault(c => c.Name == diver); @if (diverCard != null) { @diver } @diver } else { Select Diver @(idx + 1) }
}
Notes
@if (showDiverPicker) {
Select Diver @(activeDiverSlot + 1)
@foreach (var card in DiverPickerCards) {
@card.Name @card.Name
}
} @code { [Parameter] public Guid? Id { get; set; } private List AllCards => CardDatabase.Cards; private List factions = []; private string deckName = ""; private string deckSeason = ""; private string deckNotes = ""; private List deckCards = []; private List divers = []; private string search = ""; private string categoryFilter = ""; private string factionFilter = ""; private int gridColumns = 4; private bool showDiverPicker; private int activeDiverSlot; private string diverSearch = ""; private bool isSaving; private string saveMessage = ""; private bool saveError; private bool IsValid => deckCards.Count == 40 && divers.Count == 2 && divers.All(d => d != null); private string ValidationMessage => (deckCards.Count, divers.Count) switch { (< 40, _) => $"{40 - deckCards.Count} cards short", (> 40, _) => $"{deckCards.Count - 40} cards over", (40, < 2) => "Need 2 divers", _ => "Valid" }; private IEnumerable BrowserCards => AllCards .Where(c => c.Category is "Agent" or "Spell" or "Immortalized") .Where(c => categoryFilter == "" || c.Category == categoryFilter) .Where(c => factionFilter == "" || c.Faction == factionFilter) .Where(c => search == "" || c.MatchesSearch(search)); private record DeckGroup(CardData Card, int Count); private List DeckGroups => deckCards .GroupBy(name => name) .Select(g => new DeckGroup(AllCards.First(c => c.Name == g.Key), g.Count())) .OrderBy(g => g.Card.Cost ?? 99) .ThenBy(g => g.Card.Name) .ToList(); private IEnumerable DiverPickerCards { get { var inDeck = deckCards.ToHashSet(); var otherDiverSlot = activeDiverSlot == 0 ? (divers.Count > 1 ? divers[1] : null) : (divers.Count > 0 ? divers[0] : null); return AllCards .Where(c => c.Category is "Agent" or "Spell" or "Immortalized") .Where(c => !inDeck.Contains(c.Name)) .Where(c => c.Name != otherDiverSlot) .Where(c => diverSearch == "" || c.MatchesSearch(diverSearch)); } } private Dictionary ManaCurveData { get { var result = new Dictionary(); foreach (var name in deckCards) { var card = AllCards.FirstOrDefault(c => c.Name == name); if (card == null) continue; var bucket = Math.Min(card.Cost ?? 0, 10); result[bucket] = result.GetValueOrDefault(bucket) + 1; } return result; } } private Dictionary _deckCounts = new(); private void RebuildDeckCounts() => _deckCounts = deckCards.GroupBy(n => n).ToDictionary(g => g.Key, g => g.Count()); private int CountInDeck(string name) => _deckCounts.TryGetValue(name, out var c) ? c : 0; protected override async Task OnParametersSetAsync() { factions = AllCards .Where(c => c.Faction != null) .Select(c => c.Faction!) .Distinct() .OrderBy(f => f) .ToList(); if (Id.HasValue) await LoadDeck(Id.Value); } private async Task LoadDeck(Guid id) { var deck = await Db.UserDecks.FindAsync(id); if (deck == null) return; deckName = deck.Name; deckSeason = deck.Season ?? ""; deckNotes = deck.Notes ?? ""; deckCards = new List(deck.Cards); divers = new List(deck.Divers); RebuildDeckCounts(); } private void AddCard(CardData card) { if (CountInDeck(card.Name) >= 3) return; deckCards.Add(card.Name); RebuildDeckCounts(); } private void RemoveCard(string name) { var idx = deckCards.LastIndexOf(name); if (idx >= 0) deckCards.RemoveAt(idx); RebuildDeckCounts(); } private void ClearDeck() { deckCards.Clear(); RebuildDeckCounts(); } private void OpenDiverPicker(int slot) { activeDiverSlot = slot; diverSearch = ""; showDiverPicker = true; } private void CloseDiverPicker() => showDiverPicker = false; private void SelectDiver(string name) { while (divers.Count <= activeDiverSlot) divers.Add(null!); divers[activeDiverSlot] = name; showDiverPicker = false; } private void RemoveDiver(int slot) { if (slot < divers.Count) divers.RemoveAt(slot); } private async Task SaveDeck() { if (string.IsNullOrWhiteSpace(deckName)) { saveMessage = "Please enter a deck name."; saveError = true; return; } isSaving = true; saveMessage = ""; try { if (Id.HasValue) { var existing = await Db.UserDecks.FindAsync(Id.Value); if (existing != null) { existing.Name = deckName; existing.Cards = new List(deckCards); existing.Divers = new List(divers); existing.Notes = deckNotes; existing.Season = deckSeason; existing.UpdatedAt = DateTime.UtcNow; await Db.SaveChangesAsync(); saveMessage = "Saved!"; saveError = false; } } else { var deck = new UserDeck { Name = deckName, Cards = new List(deckCards), Divers = new List(divers), Notes = deckNotes, Season = deckSeason, UpdatedAt = DateTime.UtcNow }; Db.UserDecks.Add(deck); await Db.SaveChangesAsync(); saveMessage = "Saved!"; saveError = false; Nav.NavigateTo($"/deck-builder/{deck.Id}", replace: true); } } catch (Exception ex) { saveMessage = $"Error: {ex.Message}"; saveError = true; } finally { isSaving = false; } } }