mlpcardgame/src/views/DeckBuilder.vue

471 lines
10 KiB
Vue

<template>
<section class="deckbuilder">
<section class="cardlist">
<section class="filters">
<div class="row">
<b-input
@input="textChanged"
v-model="nameFilter"
placeholder="Search name"
></b-input>
<div class="colorfilter" v-for="color in colors" :key="color">
<img
@click="toggleFilter(elementFilters, color)"
:class="filterIconClass(elementFilters, color)"
:src="elementIconURL(color)"
/>
</div>
<div class="divider" />
<div class="typefilter" v-for="type in types" :key="type">
<img
@click="toggleFilter(typeFilters, type)"
:class="filterIconClass(typeFilters, type)"
:src="typeIconURL(type)"
/>
</div>
</div>
<div class="row">
<b-input
@input="textChanged"
v-model="ruleFilter"
placeholder="Search rule text"
></b-input>
<div class="setfilter" v-for="set in sets" :key="set">
<img
@click="toggleFilter(setFilters, set)"
:class="filterIconClass(setFilters, set)"
:src="setIconURL(set)"
/>
</div>
</div>
</section>
<section class="cards">
<div @click="prevPage" :class="canGoPrev ? 'prev' : 'prev unavailable'">
<img src="../assets/images/deckbuilder/navarrow.svg" />
</div>
<CardPicker
@picked="cardPicked"
:columns="columns"
:rows="rows"
:cards="currentPage"
/>
<div @click="nextPage" :class="canGoNext ? 'next' : 'next unavailable'">
<img src="../assets/images/deckbuilder/navarrow.svg" />
</div>
</section>
</section>
<section class="decklist">
<header>
<h1>{{ deckname }}</h1>
<nav class="buttons">
<b-button @click="exportToPonyhead" size="is-small deck-btn"
>Ponyhead</b-button
>
</nav>
</header>
<DeckList :cards="decklist" @removed="cardRemoved" />
</section>
</section>
</template>
<style lang="scss" scoped>
@import "@/assets/scss/_variables.scss";
.deckbuilder {
background: url("../assets/images/backgrounds/deckbuilderbg.webp") center;
background-repeat: no-repeat;
background-size: cover;
height: 100vh;
display: grid;
grid-template-columns: 3fr minmax(250px, 1fr);
}
.cardlist {
display: grid;
grid-column: 1;
grid-template-rows: 110px 1fr;
}
.filters {
display: flex;
flex-direction: column;
padding: 5px;
.row {
flex: 1;
display: flex;
align-items: center;
* {
margin: 5px;
}
}
}
.cards {
display: grid;
grid-template-columns: 5% 1fr 5%;
}
.decklist {
padding: 10px;
padding-top: 15px;
grid-column: 2;
header {
padding-left: 1rem;
color: $primary-text;
font-family: $fantasy;
h1 {
font-size: 20pt;
font-weight: 700;
margin-bottom: 5px;
}
}
}
.colorfilter,
.setfilter,
.typefilter {
cursor: pointer;
img {
opacity: 0.4;
&:hover {
opacity: 0.7;
}
&.selected {
opacity: 1;
}
}
}
.colorfilter {
width: 42px;
height: 42px;
}
.setfilter {
width: 32px;
height: 32px;
}
.typefilter {
width: 42px;
height: 42px;
}
.prev,
.next {
opacity: 0.5;
display: flex;
cursor: pointer;
&:hover {
opacity: 0.9;
}
&.unavailable {
opacity: 0.1;
cursor: not-allowed;
}
}
.next img {
transform: scaleX(-1);
}
.divider {
width: 10px;
height: 10px;
}
.deck-btn {
padding: 0 10px;
padding-bottom: 2px;
}
</style>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import DeckList from "@/components/DeckBuilder/DeckList.vue";
import CardPicker from "@/components/DeckBuilder/CardPicker.vue";
import {
Card,
CardFilter,
CardSlot,
getCards,
allSets,
cardFullName,
createPonyheadURL,
multiElemStr,
typeIndex,
rarityIndex,
colorNames,
typeNames,
cardLimit
} from "@/mlpccg";
// Sort function for sorting cards
function sortByColor(a: Card, b: Card) {
const typeA = typeIndex(a.Type);
const typeB = typeIndex(b.Type);
if (typeA != typeB) {
return typeA - typeB;
}
// Same types, filter by primary element
switch (a.Type) {
case "Friend":
case "Mane Character":
{
const elemA = multiElemStr(a.Element);
const elemB = multiElemStr(b.Element);
if (elemA > elemB) {
return 1;
}
if (elemB > elemA) {
return -1;
}
}
break;
case "Problem":
if (a.ProblemRequirement && b.ProblemRequirement) {
const preqA = multiElemStr(Object.keys(a.ProblemRequirement));
const preqB = multiElemStr(Object.keys(b.ProblemRequirement));
if (preqA > preqB) {
return 1;
}
if (preqB > preqA) {
return -1;
}
}
break;
case "Event":
case "Resource":
if (a.Requirement && b.Requirement) {
const reqA = multiElemStr(Object.keys(a.Requirement));
const reqB = multiElemStr(Object.keys(b.Requirement));
if (reqA > reqB) {
return 1;
}
if (reqB > reqA) {
return -1;
}
}
break;
}
// Filter by power
if (a.Power && b.Power) {
if (a.Power != b.Power) {
return a.Power - b.Power;
}
}
// Filter by Rarity (not the pony)
const rarityA = rarityIndex(a.Rarity);
const rarityB = rarityIndex(b.Rarity);
if (rarityA != rarityB) {
return rarityA - rarityB;
}
const nameA = cardFullName(a);
const nameB = cardFullName(b);
if (nameA > nameB) {
return 1;
}
if (nameB > nameA) {
return -1;
}
return 0;
}
@Component({
components: {
DeckList,
CardPicker
}
})
export default class DeckBuilder extends Vue {
// Picked/filtered cards
private decklist!: CardSlot[];
private filtered!: Card[];
// Names
private colors!: string[];
private sets!: string[];
private types!: string[];
// Card picker size
private rows!: number;
private columns!: number;
// User Filters
private nameFilter!: string;
private ruleFilter!: string;
private setFilters!: string[];
private elementFilters!: string[];
private typeFilters!: string[];
// Decklist options
private deckname!: string;
// Navigation
private offset!: number;
private data() {
return {
decklist: [],
filtered: [],
offset: 0,
rows: 2,
columns: 5,
nameFilter: "",
ruleFilter: "",
setFilters: [],
elementFilters: [],
typeFilters: [],
colors: colorNames,
sets: allSets.slice(0, -1),
types: typeNames,
deckname: "Unnamed deck"
};
}
private mounted() {
this.applyFilters();
}
private async applyFilters() {
let filters: CardFilter = {};
if (this.setFilters.length > 0) {
filters.Sets = this.setFilters;
}
if (this.elementFilters.length > 0) {
filters.Elements = this.elementFilters;
}
if (this.typeFilters.length > 0) {
filters.Types = this.typeFilters;
}
if (this.nameFilter.length > 0) {
filters.Name = this.nameFilter;
}
if (this.ruleFilter.length > 0) {
filters.Rules = this.ruleFilter;
}
const filtered = await getCards(filters);
this.filtered = filtered.sort(sortByColor);
this.offset = 0;
}
private get itemsPerPage() {
return this.columns * this.rows;
}
private get currentPage(): CardSlot[] {
return this.filtered
.slice(this.offset, this.offset + this.itemsPerPage)
.map(card => {
const res = this.decklist.find(c => c.data.ID == card.ID);
if (res) {
return res;
}
return {
data: card,
limit: cardLimit(card.Type),
howmany: 0
};
});
}
private elementIconURL(element: string): string {
return require(`../assets/images/elements/${element.toLowerCase()}.webp`);
}
private setIconURL(set: string): string {
return require(`../assets/images/sets/${set.toUpperCase()}.webp`);
}
private typeIconURL(type: string): string {
let urltype = type.toLowerCase();
if (urltype == "mane character") {
urltype = "mane-char";
}
return require(`../assets/images/cardtypes/${urltype}.webp`);
}
private filterIconClass(filter: string[], key: string) {
return {
selected: filter.includes(key)
};
}
private toggleFilter(filter: string[], key: string) {
const idx = filter.indexOf(key);
if (idx >= 0) {
filter.splice(idx, 1);
} else {
filter.push(key);
}
this.applyFilters();
}
private textChanged() {
this.applyFilters();
}
private get canGoPrev(): boolean {
return this.offset > 0;
}
private get canGoNext(): boolean {
return this.offset + this.itemsPerPage < this.filtered.length;
}
private prevPage() {
if (!this.canGoPrev) {
return;
}
this.offset = Math.max(0, this.offset - this.itemsPerPage);
}
private nextPage() {
if (!this.canGoNext) {
return;
}
this.offset = Math.min(
this.filtered.length - this.itemsPerPage,
this.offset + this.itemsPerPage
);
}
private cardPicked(card: Card) {
// Check if card is already in
const idx = this.decklist.findIndex(c => c.data.ID == card.ID);
if (idx >= 0) {
const deckitem = this.decklist[idx];
deckitem.howmany += 1;
Vue.set(this.decklist, idx, deckitem);
} else {
this.decklist.push({
data: card,
limit: cardLimit(card.Type),
howmany: 1
});
}
}
private exportToPonyhead() {
const url = createPonyheadURL(this.decklist.map(c => c.data));
window.open(url, "_blank");
}
private cardRemoved(card: CardSlot) {
const idx = this.decklist.findIndex(c => c.data.ID == card.data.ID);
if (idx < 0) {
throw new Error("Removing card that isn't in the deck?");
}
const deckitem = this.decklist[idx];
deckitem.howmany -= 1;
if (deckitem.howmany <= 0) {
this.decklist.splice(idx, 1);
} else {
Vue.set(this.decklist, idx, deckitem);
}
}
}
</script>