Compare commits

...

3 commits

5 changed files with 288 additions and 220 deletions

View file

@ -0,0 +1,174 @@
'use strict'
import CardsDB from '@/plugins/ffdecks'
export default class {
constructor() {
this.id = null
this.name = ''
this.note = ''
this.cards = []
}
from_plainObject(obj) {
this.id = obj.id
this.name = obj.content.name
this.note = obj.content.note
this.cards = obj.content.cards
}
from_deckList(str) {
// select all lines containing card serial numbers
let cardLinesRE = /^.*\b\d-\d{3}[A-Z]?\b.*$/gm
let cardLines = str.match(cardLinesRE)
let cardCounts = {}
cardLines.forEach(cardLine => {
// extract serial (guaranteed to be in here!)
let serialRE = /\b(\d-\d{3})[A-Z]?\b/i
let serial = serialRE.exec(cardLine)[1]
// strip out serial number
cardLine = cardLine.replace(serialRE, '')
let countREs = [
// prioritize a count with "times" symbol *, x, ×
/\b([0-9]+)(?:[*×]|[x]\b)/,
/(?:[*×]|\b[x])([0-9]+)\b/,
// next priority: count with whitespace
/\s+([0-9]+)\s+/,
/\s+([0-9]+)\b/,
/\b([0-9]+)\s+/,
// least priority: any simple number
/\b([0-9]+)\b/
]
// fallback value
let count = 1
for (let countRE of countREs) {
let data = countRE.exec(cardLine)
if (data) {
count = Number(data[1])
break
}
}
// count copies
if (!cardCounts[serial]) {
cardCounts[serial] = 0
}
cardCounts[serial] += count
})
// push card data into deck
this.cards = []
for(let serial in cardCounts) {
this.cards.push({
serial: serial,
count: cardCounts[serial]
})
}
// strip out lines with serial numbers
str = str.replace(cardLinesRE, '')
// then strip out anything after the first empty line
str = str.replace(/^[\s]*$[^]*/m, '')
// select the line containing 'deck name:'
// and its successor (ffdecks format)
let metaRE = /^Deck Name: (.+)$[\s]*?^(.+)$/m
let metaData = metaRE.exec(str)
// fallback
this.name = 'Unnamed Deck'
this.note = ''
if (!metaData) {
// use lax format: <anything>:[deck name][newline][note]
metaRE = /[^]*?:(.+)$[\s]*?^([^]*)/m
metaData = metaRE.exec(str)
}
// look again, I am not an else!
if (metaData) {
// extract matches
this.name = metaData[1].trim()
this.note = metaData[2].trim()
}
}
plainObject() {
return {
name: this.name,
note: this.note,
cards: this.cards
}
}
parts() {
let retval = ['Forwards', 'Backups', 'Summons, Monsters & more'].map(
item => ({
heading: item,
cards: [],
count: 0
})
)
for (let card of this.cards) {
let target
switch (card.dbentry.type) {
case 'Forward':
target = 0
break
case 'Backup':
target = 1
break
default:
target = 2
break
}
retval[target].cards.push(card)
retval[target].count += card.count
}
for (let part of retval) {
part.cards.sort(
(card_l, card_r) => card_l.dbentry.cost - card_r.dbentry.cost
)
}
return retval
}
deckList() {
let lines = []
// begin with deck name and note
lines.push('Deck Name: ' + this.name)
lines.push(this.note)
lines.push('')
// list each deck part
for(let part of this.parts()) {
lines.push(`${part.heading} (${part.count}):`)
for(let card of part.cards)
lines.push(`${card.count}x ${card.serial} "${card.dbentry.name}"`)
lines.push('')
}
return lines.join('\n')
}
count() {
return this.cards.reduce((total, card) => total + card.count, 0)
}
populate() {
for (let card of this.cards) {
card.dbentry = CardsDB[card.serial]
}
}
}

View file

@ -10,7 +10,7 @@
<v-container v-if="!editing" style="position: relative" grid-list-md fluid> <v-container v-if="!editing" style="position: relative" grid-list-md fluid>
<v-layout row wrap> <v-layout row wrap>
<v-flex v-for="part in deck_parts" :key="part.heading" xs12 sm6 md4> <v-flex v-for="part in deck.parts()" :key="part.heading" xs12 sm6 md4>
<v-card> <v-card>
<v-card-title>{{ part.count }} {{ part.heading }}</v-card-title> <v-card-title>{{ part.count }} {{ part.heading }}</v-card-title>
<v-list dense subheader> <v-list dense subheader>
@ -30,16 +30,20 @@
</v-btn> </v-btn>
</v-layout> </v-layout>
</v-container> </v-container>
<DeckEditor <DeckEditor
:visible="editing" :visible="editing"
:id="deck.id" :deck="deck"
:value="deck_list"
@close="editing = false" @close="editing = false"
@change="change_deck"
/> />
</v-expansion-panel-content> </v-expansion-panel-content>
</template> </template>
<script> <script>
import * as Cookies from 'js-cookie'
import axios from '@/plugins/axios'
import Card from './Card.vue' import Card from './Card.vue'
import DeckEditor from './forms/DeckEditor.vue' import DeckEditor from './forms/DeckEditor.vue'
@ -60,60 +64,24 @@ export default {
}), }),
computed: { computed: {
deck_parts() { session: () => Cookies.get('session')
let retval = ['Forwards', 'Backups', 'Summons, Monsters & more'].map(
item => ({
heading: item,
cards: [],
count: 0
})
)
this.deck.cards.forEach(card => {
let target
switch (card.dbentry.type) {
case 'Forward':
target = 0
break
case 'Backup':
target = 1
break
default:
target = 2
break
}
retval[target].cards.push(card)
retval[target].count += card.count
})
retval.forEach(part =>
part.cards.sort(
(card_l, card_r) => card_l.dbentry.cost - card_r.dbentry.cost
)
)
return retval
}, },
deck_list() { methods: {
let lines = [] change_deck(new_deck) {
axios
// begin with deck name and note .post('/decks/modify', {
lines.push('Deck Name: ' + this.deck.name) session: this.session,
lines.push(this.deck.note) deckID: this.deck.id,
lines.push('') deckCards: new_deck
})
// list each deck part .then(response => {
this.deck_parts.forEach(part => { if (response.data.success) {
lines.push(`${part.heading} (${part.count}):`) this.$emit('change')
} else {
part.cards.forEach(card => this.editing = true
lines.push(`${card.count}x ${card.serial} "${card.dbentry.name}"`) }
)
lines.push('')
}) })
return lines.join('\n')
} }
} }
} }

View file

@ -1,21 +1,29 @@
<template> <template>
<v-expansion-panel v-if="linked_decks"> <v-expansion-panel>
<Deck v-for="deck in linked_decks" :deck="deck" :key="deck.id" /> <Deck
v-for="deck in linked"
:deck="deck"
:key="deck.id"
@change="refresh_decks"
/>
<NewDeck />
</v-expansion-panel> </v-expansion-panel>
</template> </template>
<script> <script>
import * as Cookies from 'js-cookie' import * as Cookies from 'js-cookie'
import axios from '@/plugins/axios' import axios from '@/plugins/axios'
import CardsDB from '@/plugins/ffdecks' import DeckJS from '@/classes/Deck'
import Deck from './Deck.vue' import Deck from './Deck.vue'
import NewDeck from './NewDeck.vue'
export default { export default {
name: 'DeckList', name: 'DeckList',
components: { components: {
Deck Deck,
NewDeck
}, },
asyncComputed: { asyncComputed: {
@ -38,33 +46,25 @@ export default {
computed: { computed: {
session: () => Cookies.get('session'), session: () => Cookies.get('session'),
linked_decks() { linked() {
let result = [] let result = []
for (let i = 0; i < this.decks.length; i++) { for (let plainDeck of this.decks) {
let elem = this.decks[i] let deck = new DeckJS()
let cards = [] deck.from_plainObject(plainDeck)
deck.populate()
for (let j = 0; j < elem.content.cards.length; j++) { result.push(deck)
let card = elem.content.cards[j]
cards.push({
count: card.count,
serial: card.serial,
dbentry: CardsDB[card.serial]
})
}
result.push({
id: elem.id,
name: elem.content.name,
note: elem.content.note,
cards: cards
})
} }
return result return result
} }
},
methods: {
refresh_decks() {
this.$asyncComputed.decks.update()
}
} }
} }
</script> </script>

View file

@ -0,0 +1,25 @@
<template>
<v-expansion-panel-content>
<template v-slot:header>
<span class="subheading">New Deck</span>
</template>
<v-container>
<v-card flat>
<!-- <DeckEditor :visible="true" value="" @close="" @change="" /> -->
</v-card>
</v-container>
</v-expansion-panel-content>
</template>
<script>
import DeckEditor from './forms/DeckEditor.vue'
export default {
name: 'NewDeck',
components: {
DeckEditor
}
}
</script>

View file

@ -1,22 +1,21 @@
<template> <template>
<v-container v-if="visible"> <v-container v-if="visible">
<v-card flat> <v-card flat>
<v-alert :value="count !== 50" type="warning"> <v-alert :value="check.count !== 50" type="warning">
{{ count }} cards detected! (Decks should have exactly 50 cards) {{ check.count }} cards detected! (Decks should have exactly 50 cards)
</v-alert> </v-alert>
<v-alert :value="maximum > 3" type="warning"> <v-alert :value="check.maximum > 3" type="warning">
Card with {{ maximum }} copies detected! (Cards should not have more Card with {{ check.maximum }} copies detected! (Cards should not have
than 3 copies) more than 3 copies)
</v-alert> </v-alert>
<v-textarea <v-textarea
ref="textarea"
label="Edit Deck" label="Edit Deck"
:value="value" v-model="new_decklist"
rows="35" rows="35"
hint="Change card counts and/or serial numbers. Names will be updated accordingly!" hint="Change card counts and/or serial numbers. Names will be updated accordingly!"
style="font-family: monospace" style="font-family: monospace"
@input="checked = false" @input="check.checked = false"
> >
</v-textarea> </v-textarea>
@ -28,12 +27,16 @@
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="info" @click.native="check"> <v-btn color="info" @click.native="check_deck">
<v-icon>check</v-icon> <v-icon>check</v-icon>
validate validate
</v-btn> </v-btn>
<v-btn color="success" @click.native="save" :disabled="!checked"> <v-btn
color="success"
@click.native="save_deck"
:disabled="!check.checked"
>
<v-icon>save</v-icon> <v-icon>save</v-icon>
save save
</v-btn> </v-btn>
@ -43,166 +46,64 @@
</template> </template>
<script> <script>
import * as Cookies from 'js-cookie' import Deck from '@/classes/Deck'
import axios from '@/plugins/axios'
export default { export default {
name: 'DeckEditor', name: 'DeckEditor',
props: { props: {
id: Number, deck: Object,
value: String,
visible: Boolean visible: Boolean
}, },
data: () => ({ data: () => ({
check: {
count: 50, count: 50,
maximum: 0, maximum: 0,
checked: false checked: false
},
new_decklist: null
}), }),
computed: { created() {
session: () => Cookies.get('session'), this.new_decklist = this.deck.deckList()
},
computed: {
new_deck() { new_deck() {
try { let deck = new Deck()
return this.parse_deck(this.$refs.textarea) deck.from_deckList(this.new_decklist)
} catch (e) { deck.populate()
return this.parse_deck(this.value)
} this.new_decklist = deck.deckList()
return deck
} }
}, },
methods: { methods: {
parse_deck(deck_string) {
let retval = {
name: '',
note: '',
cards: [],
count: 0
}
// select all lines containing card serial numbers
let cardLinesRE = /^.*\b\d-\d{3}[A-Z]?\b.*$/gm
let cardLines = deck_string.match(cardLinesRE)
let cardCounts = {}
cardLines.forEach(cardLine => {
// extract serial (guaranteed to be in here!)
let serialRE = /\b(\d-\d{3})[A-Z]?\b/i
let serial = serialRE.exec(cardLine)[1]
// strip out serial number
cardLine = cardLine.replace(serialRE, '')
let countREs = [
// prioritize a count with "times" symbol *, x, ×
/\b([0-9]+)(?:[*×]|[x]\b)/,
/(?:[*×]|\b[x])([0-9]+)\b/,
// next priority: count with whitespace
/\s+([0-9]+)\s+/,
/\s+([0-9]+)\b/,
/\b([0-9]+)\s+/,
// least priority: any simple number
/\b([0-9]+)\b/
]
// fallback value
let count = 1
for (let i = 0; i < countREs.length; i++) {
let data = countREs[i].exec(cardLine)
if (data) {
count = Number(data[1])
break
}
}
// count copies
if (!cardCounts[serial]) {
cardCounts[serial] = 0
}
cardCounts[serial] += count
retval.count += count
})
// push card data into deck
Object.keys(cardCounts).forEach(serial => {
retval.cards.push({
serial: serial,
count: cardCounts[serial]
})
})
// strip out lines with serial numbers
deck_string = deck_string.replace(cardLinesRE, '')
// select the line containing 'deck name:'
// and its successor (ffdecks format)
let metaRE = /^Deck Name: (.+)$[\s]*?^(.+)$/m
let metaData = metaRE.exec(deck_string)
// fallback
retval.name = 'Unnamed Deck'
retval.note = ''
if (!metaData) {
// no ffdecks format found: strip out anything after the first empty line
deck_string = deck_string.replace(/^[\s]*$[^]*/m, '')
// use lax format: <anything>:[deck name][newline][note]
metaRE = /[^]*?:(.+)$[\s]*?^([^]*)/m
metaData = metaRE.exec(deck_string)
}
// look again, I am not an else!
if (metaData) {
// extract matches
retval.name = metaData[1].trim()
retval.note = metaData[2].trim()
}
return retval
},
close() { close() {
this.checked = false this.check.checked = false
this.$emit('close') this.$emit('close')
}, },
check() { check_deck() {
let new_deck = this.parse_deck(this.$refs.textarea.lazyValue)
// count number of cards // count number of cards
this.count = new_deck.count this.check.count = this.new_deck.count()
// find most frequent card // find most frequent card
this.maximum = 0 this.check.maximum = 0
new_deck.cards.forEach(card => { this.new_deck.cards.forEach(card => {
if (card.count > this.maximum) this.maximum = card.count if (card.count > this.check.maximum) this.check.maximum = card.count
}) })
// deck has now been checked // deck has now been checked
this.checked = true this.check.checked = true
}, },
save() { save_deck() {
let new_deck = this.parse_deck(this.$refs.textarea.lazyValue) this.$emit('change', this.new_deck.plainObject())
axios
.post('/decks/modify', {
session: this.session,
deckID: this.id,
deckCards: new_deck
})
.then(response => {
if (response.data.success) {
this.$emit('change', new_deck)
this.close() this.close()
} }
})
}
} }
} }
</script> </script>