2019-09-04 10:19:29 +00:00
|
|
|
<template>
|
2019-09-12 09:11:32 +00:00
|
|
|
<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>
|
2019-09-04 10:19:29 +00:00
|
|
|
</template>
|
2019-09-02 16:02:40 +00:00
|
|
|
|
2019-09-12 09:11:32 +00:00
|
|
|
<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>
|
2019-09-02 16:02:40 +00:00
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import { Component, Vue } from "vue-property-decorator";
|
2019-09-12 09:11:32 +00:00
|
|
|
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;
|
|
|
|
}
|
2019-09-02 16:02:40 +00:00
|
|
|
|
|
|
|
@Component({
|
2019-09-12 09:11:32 +00:00
|
|
|
components: {
|
|
|
|
DeckList,
|
|
|
|
CardPicker
|
|
|
|
}
|
2019-09-02 16:02:40 +00:00
|
|
|
})
|
2019-09-12 09:11:32 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-04 12:56:02 +00:00
|
|
|
</script>
|