Compare commits

...

4 commits

Author SHA1 Message Date
9616a2fe97 Delete deck 2019-05-24 13:41:48 +02:00
0475e45275 Add new deck 2019-05-24 13:30:26 +02:00
38b92829cd zoom cursor 2019-05-24 11:26:02 +02:00
0eeaa08239 DeckEditor v-model untangling 2019-05-24 11:25:35 +02:00
10 changed files with 270 additions and 118 deletions

View file

@ -210,10 +210,10 @@ class FFTCGDB
logger.debug "getDecks: OK '#{userID}'"
resolve (id: row.deck, content: JSON.parse row.json for row, i in rows)
delDeck: (deckID) ->
delDeck: (userID, deckID) ->
new Promise (resolve, reject) =>
stmt = @db.prepare 'DELETE FROM decks WHERE deck = ?'
stmt.run [deckID], (err) ->
stmt = @db.prepare 'DELETE FROM decks WHERE deck = ? AND user = ?'
stmt.run [deckID, userID], (err) ->
stmt.finalize()
if err
logger.warn "delDeck: FAIL db '#{err.code}' for '#{deckID}'"

View file

@ -0,0 +1,34 @@
logger = (require 'logging').default '/decks/add'
# session storage (volatile data)
session = (require '../../session')
# fftcg.db (persistent data)
fftcgdb = (require '../../db')
module.exports =
url: '/decks/add'
method: 'POST'
# schema: (require './modify.schema')
handler: (request, reply) ->
session.check request.body.session ? ""
.then (userid) ->
# active session found, get associated user
fftcgdb.addDeck (userid), (request.body.deckCards)
.then (deckID) ->
logger.info "OK user '#{userid}' added deck '#{deckID}'"
reply.send
success: true
deck: deckID
.catch (err) ->
# couldnt get user details
logger.warn "FAIL '#{err}' for user id '#{userid}'"
reply.send
success: false
.catch ->
# no session found
logger.info "FAIL '#{request.body.session}' session not found"
reply.send
success: false

View file

@ -0,0 +1,34 @@
logger = (require 'logging').default '/decks/delete'
# session storage (volatile data)
session = (require '../../session')
# fftcg.db (persistent data)
fftcgdb = (require '../../db')
module.exports =
url: '/decks/delete'
method: 'POST'
# schema: (require './modify.schema')
handler: (request, reply) ->
session.check request.body.session ? ""
.then (userid) ->
# active session found, get associated user
fftcgdb.delDeck (userid), (request.body.deckID)
.then (deckID) ->
logger.info "OK user '#{userid}' deleted deck '#{deckID}'"
reply.send
success: true
deck: deckID
.catch (err) ->
# couldnt get user details
logger.warn "FAIL '#{err}' for user id '#{userid}'"
reply.send
success: false
.catch ->
# no session found
logger.info "FAIL '#{request.body.session}' session not found"
reply.send
success: false

View file

@ -22,8 +22,12 @@ fastify.route (require "./routes/#{route}") for route in [
'user/register'
# list decks
'decks/list'
# add deck
'decks/add'
# modify deck
'decks/modify'
# delete deck
'decks/delete'
]
# request logging

View file

@ -3,67 +3,94 @@
import CardsDB from '@/plugins/ffdecks'
export default class {
constructor() {
this.id = null
constructor(id) {
this.id = id
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
populate() {
for (let card of this.cards) {
card.dbentry = CardsDB[card.serial]
}
}
from_object(obj) {
if (obj) {
this.name = obj.name
this.note = obj.note
this.cards = obj.cards
}
}
plainObject() {
let plainCards = []
for (let card of this.cards) {
plainCards.push({
serial: card.serial,
count: card.count
})
}
return {
name: this.name,
note: this.note,
cards: plainCards
}
}
from_deckList(str) {
// select all lines containing card serial numbers
let cardLinesRE = /^.*\b\d-\d{3}[A-Z]?\b.*$/gm
let cardLinesRE = /^.*\b\d+-0*\d{1,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]
if (cardLines) {
for (let cardLine of cardLines) {
// extract serial (guaranteed to be in here!)
let serialRE = /\b(\d+)-0*(\d{1,3})[A-Z]?\b/i
let serial = serialRE.exec(cardLine)
// force format 'x-xxx'
serial = `${serial[1]}-${serial[2].padStart(3, '0')}`
// strip out serial number
cardLine = cardLine.replace(serialRE, '')
// 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/
]
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)
// fallback value
let count = 1
for (let countRE of countREs) {
let data = countRE.exec(cardLine)
if (data) {
count = Number(data[1])
break
if (data) {
count = Number(data[1])
break
}
}
}
// count copies
if (!cardCounts[serial]) {
cardCounts[serial] = 0
// count copies
if (!cardCounts[serial]) {
cardCounts[serial] = 0
}
cardCounts[serial] += count
}
cardCounts[serial] += count
})
}
// push card data into deck
this.cards = []
for(let serial in cardCounts) {
for (let serial in cardCounts) {
this.cards.push({
serial: serial,
count: cardCounts[serial]
@ -98,14 +125,6 @@ export default class {
}
}
plainObject() {
return {
name: this.name,
note: this.note,
cards: this.cards
}
}
parts() {
let retval = ['Forwards', 'Backups', 'Summons, Monsters & more'].map(
item => ({
@ -150,10 +169,10 @@ export default class {
lines.push('')
// list each deck part
for(let part of this.parts()) {
for (let part of this.parts()) {
lines.push(`${part.heading} (${part.count}):`)
for(let card of part.cards)
for (let card of part.cards)
lines.push(`${card.count}x ${card.serial} "${card.dbentry.name}"`)
lines.push('')
@ -165,10 +184,4 @@ export default class {
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

@ -2,14 +2,14 @@
<v-list-tile avatar>
<v-tooltip @input="booted = true" bottom>
<template v-slot:activator="{ on }">
<v-list-tile-avatar v-on="on">
<v-list-tile-avatar v-on="on" style="cursor: zoom-in">
<svg
x="0px"
y="0px"
width="16px"
height="30px"
viewBox="0 0 16 30"
style="enable-background:new 0 0 16 30;"
style="enable-background:new 0 0 16 30"
xml:space="preserve"
>
<polygon points="0,5 8,0 16,5 16,25 8,30 0,25" :fill="color" />

View file

@ -28,15 +28,49 @@
<v-btn fab absolute bottom right @click.native="editing = true">
<v-icon>edit</v-icon>
</v-btn>
<v-dialog v-model="deleting" persistent>
<v-btn fab absolute bottom left slot="activator">
<v-icon>delete</v-icon>
</v-btn>
<v-card>
<v-card-title class="headline">
Really delete this deck?
</v-card-title>
<v-card-text>
Are you sure you want to delete this deck? This cannot be undone.
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" @click.native="deleting = false">
Cancel
</v-btn>
<v-btn
color="success"
@click.native="
deleting = false
delete_deck()
"
>
Confirm
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-layout>
</v-container>
<DeckEditor
:visible="editing"
:deck="deck"
@close="editing = false"
@change="change_deck"
/>
<DeckEditor v-if="editing" :deck="deck" @save="save_deck">
<v-btn color="error" @click.native="editing = false">
<v-icon>cancel</v-icon>
cancel
</v-btn>
</DeckEditor>
</v-expansion-panel-content>
</template>
@ -60,7 +94,8 @@ export default {
},
data: () => ({
editing: false
editing: false,
deleting: false
}),
computed: {
@ -68,18 +103,30 @@ export default {
},
methods: {
change_deck(new_deck) {
save_deck(new_deck) {
axios
.post('/decks/modify', {
session: this.session,
deckID: this.deck.id,
deckCards: new_deck
deckCards: new_deck.plainObject()
})
.then(response => {
if (response.data.success) {
this.editing = false
this.$emit('change')
}
})
},
delete_deck() {
axios
.post('/decks/delete', {
session: this.session,
deckID: this.deck.id
})
.then(response => {
if (response.data.success) {
this.$emit('change')
} else {
this.editing = true
}
})
}

View file

@ -1,12 +1,17 @@
<template>
<v-expansion-panel>
<v-expansion-panel v-model="open">
<Deck
v-for="deck in linked"
:deck="deck"
:key="deck.id"
@change="refresh_decks"
/>
<NewDeck />
<NewDeck
@change="
open = null
refresh_decks()
"
/>
</v-expansion-panel>
</template>
@ -26,6 +31,10 @@ export default {
NewDeck
},
data: () => ({
open: null
}),
asyncComputed: {
decks: {
get() {
@ -50,8 +59,9 @@ export default {
let result = []
for (let plainDeck of this.decks) {
let deck = new DeckJS()
deck.from_plainObject(plainDeck)
let deck = new DeckJS(plainDeck.id)
deck.from_object(plainDeck.content)
deck.populate()
result.push(deck)

View file

@ -6,13 +6,16 @@
<v-container>
<v-card flat>
<!-- <DeckEditor :visible="true" value="" @close="" @change="" /> -->
<DeckEditor ref="editor" @save="save_deck" />
</v-card>
</v-container>
</v-expansion-panel-content>
</template>
<script>
import * as Cookies from 'js-cookie'
import axios from '@/plugins/axios'
import DeckEditor from './forms/DeckEditor.vue'
export default {
@ -20,6 +23,26 @@ export default {
components: {
DeckEditor
},
computed: {
session: () => Cookies.get('session')
},
methods: {
save_deck(new_deck) {
axios
.post('/decks/add', {
session: this.session,
deckCards: new_deck.plainObject()
})
.then(response => {
if (response.data.success) {
this.$emit('change')
this.$refs.editor.clear()
}
})
}
}
}
</script>

View file

@ -1,5 +1,5 @@
<template>
<v-container v-if="visible">
<v-container>
<v-card flat>
<v-alert :value="check.count !== 50" type="warning">
{{ check.count }} cards detected! (Decks should have exactly 50 cards)
@ -10,33 +10,26 @@
</v-alert>
<v-textarea
ref="deckList"
label="Edit Deck"
v-model="new_decklist"
rows="35"
hint="Change card counts and/or serial numbers. Names will be updated accordingly!"
style="font-family: monospace"
:value="new_deck.deckList()"
@input="check.checked = false"
>
</v-textarea>
<v-card-actions>
<v-btn color="error" @click.native="close">
<v-icon>cancel</v-icon>
cancel
</v-btn>
<slot></slot>
<v-spacer></v-spacer>
<v-btn color="info" @click.native="check_deck">
<v-btn color="info" @click.native="validate" :disabled="check.checked">
<v-icon>check</v-icon>
validate
</v-btn>
<v-btn
color="success"
@click.native="save_deck"
:disabled="!check.checked"
>
<v-btn color="success" @click.native="save" :disabled="!check.checked">
<v-icon>save</v-icon>
save
</v-btn>
@ -52,57 +45,51 @@ export default {
name: 'DeckEditor',
props: {
deck: Object,
visible: Boolean
deck: Object
},
data: () => ({
check: {
count: 50,
maximum: 0,
checked: false
},
new_decklist: null
check: null,
new_deck: null
}),
created() {
this.new_decklist = this.deck.deckList()
},
computed: {
new_deck() {
let deck = new Deck()
deck.from_deckList(this.new_decklist)
deck.populate()
this.new_decklist = deck.deckList()
return deck
}
this.clear()
},
methods: {
close() {
this.check.checked = false
this.$emit('close')
clear() {
this.check = {
count: 50,
maximum: 0,
checked: false
}
this.new_deck = new Deck(0)
if (this.deck)
// this.deck should already be populated!
this.new_deck.from_object(this.deck)
},
check_deck() {
validate() {
this.new_deck.from_deckList(this.$refs.deckList.lazyValue)
this.new_deck.populate()
// count number of cards
this.check.count = this.new_deck.count()
// find most frequent card
this.check.maximum = 0
this.new_deck.cards.forEach(card => {
for (let card of this.new_deck.cards) {
if (card.count > this.check.maximum) this.check.maximum = card.count
})
}
// deck has now been checked
this.check.checked = true
},
save_deck() {
this.$emit('change', this.new_deck.plainObject())
this.close()
save() {
this.$emit('save', this.new_deck)
}
}
}