generalize notifications, popups via teleport, create/edit decks

This commit is contained in:
koehr 2020-07-03 14:58:08 +02:00
parent ed8bcda86d
commit 2062beaefb
12 changed files with 251 additions and 61 deletions

View file

@ -8,6 +8,11 @@
<main>
<router-view />
</main>
<div id="popup" v-show="popupShown">
<div class="popup-content">
</div>
</div>
</template>
<script lang='ts'>
@ -18,14 +23,21 @@ import Notifications from '@/components/Notifications.vue'
export default defineComponent({
setup () {
const { collection: notifications, actions } = useState('notifications')
const { collection: popupShown } = useState('popup')
const { collection: notifications, actions: notificationActions } = useState('notifications')
return {
popupShown,
notifications,
addNotification: actions.add,
dismissNotification: actions.dismiss
addNotification: notificationActions.add,
dismissNotification: notificationActions.dismiss,
}
},
components: { Notifications, Logo },
data () {
return {
showPopup: false
}
},
watch: {
'$route' (newRoute) {
const bodyEl = document.body

View file

@ -9,7 +9,8 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { KV, CardSize } from '@/types'
import { KV } from '@/types'
import { CardSize, defaultCardSize } from '@/consts'
import { cardSizeToStyle } from '@/lib/card'
import iconPath from '@/lib/iconPath'
@ -32,7 +33,7 @@ export default defineComponent({
},
cssVars (): KV<string> {
const backgroundColor = this.color || 'transparent'
const size = this.size as CardSize || CardSize.Poker
const size = this.size as CardSize || defaultCardSize
return {
backgroundColor,
...cardSizeToStyle(size)

View file

@ -0,0 +1,17 @@
<template>
<Card :icon="deck.icon" :color="deck.color" :size="deck.cardSize">
<template #back>{{ deck.name }} ({{ deck.cards.length }})</template>
</Card>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Card from '@/components/Card.vue'
export default defineComponent({
components: { Card },
props: {
deck: Object
}
})
</script>

View file

@ -0,0 +1,65 @@
<template>
<form class="options-form" @submit.prevent="saveDeck">
<div class="deck-form-fields">
<select v-model="icon">
<option :key="iconName" :value="iconName" v-for="iconName in icons">{{ iconName }}</option>
</select>
<input v-model="name" title="deck name" placeholder="give it a name" />
<input v-model="description" title="deck description" placeholder="the most awesome deck of cards" />
<p>Pick a colour: <input type="color" v-model="color" /></p>
<select v-model="cardSize">
<option :key="size.value" :value="size.value" v-for="size in sizes">{{ size.title }}</option>
</select>
<button type="submit">Save deck</button>
<button class="cancel" @click.prevent="$emit('close')">cancel</button>
</div>
<DeckCard :deck="{ icon, name, description, color, cardSize, cards: [] }" />
</form>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { useState } from '@/state'
import { cardSizeOptions, defaultCardSize } from '@/consts'
import DeckCard from '@/components/DeckCard.vue'
export default defineComponent({
components: { DeckCard },
props: {
deck: Object
},
data () {
const icons = useState('icons').collection
return {
icons,
sizes: cardSizeOptions,
icon: icons[0],
name: 'no-name deck',
description: '',
color: '#3C1C00',
cardSize: defaultCardSize
}
},
watch: {
deck (deck, oldDeck) {
this.icon = deck.icon
this.name = deck.name
this.description = deck.description
this.color = deck.color
this.cardSize = deck.cardSize
}
},
methods: {
saveDeck () {
const { icon, name, description, color, cardSize } = this
this.$emit('save', { icon, name, description, color, cardSize })
}
}
})
</script>

50
src/consts.ts Normal file
View file

@ -0,0 +1,50 @@
// page width x page height
export const enum PageSize {
A4 = '210mm 297mm',
USLetter = '8.5in 11in',
JISB4 = '182mm 257mm',
A3 = '297mm 420mm',
A5 = '148mm 210mm',
USLegal = '8.5in 14in',
USLedger = '11in 17in',
JISB5 = '257mm 364mm'
}
// card width x card height
export const enum CardSize {
Poker = '64x89',
Bridge = '57x89'
}
export const enum Arrangement {
DoubleSided = 'doublesided',
FrontOnly = 'frontonly',
SideBySide = 'sidebyside'
}
export const cardSizeOptions = [
{ title: '88x62 (Poker)', value: CardSize.Poker },
{ title: '88x56 (Bridge)', value: CardSize.Bridge }
]
export const pageSizeOptions = [
{ title: 'A4', value: PageSize.A4 }, // 210mm × 297mm
{ title: 'US Letter', value: PageSize.USLetter }, // 8.5in × 11in
{ title: 'JIS-B4', value: PageSize.JISB4 }, // 182mm × 257mm
{ title: 'A3', value: PageSize.A3 }, // 297mm × 420mm
{ title: 'A5', value: PageSize.A5 }, // 148mm × 210mm
{ title: 'US Legal', value: PageSize.USLegal }, // 8.5in × 14in
{ title: 'US Ledger', value: PageSize.USLedger }, // 11in × 17in
{ title: 'JIS-B5', value: PageSize.JISB5 } // 257mm × 364mm
]
export const arrangementOptions = [
{ title: 'Double Sided', value: Arrangement.DoubleSided },
{ title: 'Only Front Sides', value: Arrangement.FrontOnly },
{ title: 'Side by Side', value: Arrangement.SideBySide }
]
export const defaultPageSize = pageSizeOptions[0].value
export const defaultCardSize = cardSizeOptions[0].value
export const defaultArrangement = arrangementOptions[0].value

View file

@ -1,4 +1,5 @@
import { ICard, CardSize } from '../types'
import { CardSize } from '../consts'
import { ICard } from '../types'
export function defaultCard (): ICard {
return {

View file

@ -1,26 +1,29 @@
import { IDeck, CardSize, PageSize, Arrangement } from '../types'
import { CardSize, PageSize, Arrangement } from '../consts'
import { IDeck } from '../types'
export const defaultDeckValues: IDeck = {
id: 0,
icon: 'robe',
name: 'the nameless',
description: '',
color: '#3C1C00',
cards: [],
cardSize: CardSize.Poker,
pageSize: PageSize.A4,
arrangement: Arrangement.DoubleSided,
roundedCorners: true
}
export function defaultDeck (): IDeck {
return {
id: 0,
icon: 'robe',
name: 'the nameless',
description: '',
color: '#3C1C00',
cards: [],
cardSize: CardSize.Poker,
pageSize: PageSize.A4,
arrangement: Arrangement.DoubleSided,
roundedCorners: true
}
return { ...defaultDeckValues }
}
export function isValidDeck (deck: any): boolean {
const example = defaultDeck() as { [key: string]: any }
const example = defaultDeckValues as { [key: string]: any }
for (const key in example) {
const type = typeof example[key]
return typeof deck[key] === type
if (typeof deck[key] !== type) return false
}
return true

View file

@ -1,6 +1,6 @@
import { Ref } from 'vue'
import { Notification, IDeck, KV } from '../types'
import { defaultDeck } from '../lib/deck'
import { defaultDeck, defaultDeckValues } from '../lib/deck'
/// actions are called like action['sub/foo'](state.sub, payload)
export default {
@ -22,7 +22,37 @@ export default {
},
// DECK ACTIONS
'decks/new' (decks: Ref<IDeck[]>) {
decks.value.push(defaultDeck())
}
// returns index of newly created deck
'decks/new' (decks: Ref<IDeck[]>): number {
const newDeck = defaultDeck()
const id = decks.value.push(newDeck) - 1
newDeck.id = id
return id
},
// updates decks[updatedDeck.id]
'decks/update' (decks: Ref<IDeck[]>, updatedDeck: IDeck): boolean {
const id = updatedDeck.id
if (!id || !decks.value[id]) return false // can't update non-existing deck
decks.value[id] = {
...decks.value[id],
...updatedDeck
}
return true
},
// POPUP ACTIONS
'popup/show' (popup: Ref<boolean>): boolean {
popup.value = true
return popup.value
},
'popup/hide' (popup: Ref<boolean>): boolean {
popup.value = false
return popup.value
},
'popup/toggle' (popup: Ref<boolean>): boolean {
popup.value = !popup.value
return popup.value
},
}

View file

@ -9,7 +9,8 @@ const state: State = {
settings: ref({}),
decks: ref([]),
notifications: ref([]),
initialized: ref(false)
icons: ref(['mouth-watering', 'robe', 'thorny-triskelion']),
popup: ref(false)
}
export function useState (prop: string): { [key: string]: any } {

View file

@ -1,5 +1,6 @@
import Dexie from 'dexie'
import { IDeck, ICard, CardSize, Arrangement, PageSize } from './types'
import { CardSize, Arrangement, PageSize } from './consts'
import { IDeck, ICard } from './types'
interface IDeckTable {
id: number;

View file

@ -1,28 +1,5 @@
import { Ref } from 'vue'
// page width x page height
export const enum PageSize {
A4 = '210mm 297mm',
USLetter = '8.5in 11in',
JISB4 = '182mm 257mm',
A3 = '297mm 420mm',
A5 = '148mm 210mm',
USLegal = '8.5in 14in',
USLedger = '11in 17in',
JISB5 = '257mm 364mm'
}
// card width x card height
export const enum CardSize {
Poker = '64x89',
Bridge = '57x89'
}
export const enum Arrangement {
DoubleSided = 'doublesided',
FrontOnly = 'frontonly',
SideBySide = 'sidebyside'
}
import { PageSize, CardSize, Arrangement } from './consts'
export interface KV<V> {
[key: string]: V;
@ -82,5 +59,6 @@ export interface State {
settings: Ref<Settings>;
decks: Ref<IDeck[]>;
notifications: Ref<Notification[]>;
initialized: Ref<boolean>;
icons: Ref<string[]>;
popup: Ref<boolean>;
}

View file

@ -3,32 +3,63 @@
<section name="deck-covers" class="cards" :class="{ centered: !decks.length }">
<router-link :to="{ name: 'Deck', params: { id: deck.id } }" :key="deck.id" v-for="deck in decks">
<Card :icon="deck.icon" :color="deck.color" :size="deck.cardSize">
<template #back>{{ deck.name }} ({{ deck.cards.length }})</template>
</Card>
<DeckCard :deck="deck" />
</router-link>
<Card id="_add_deck" @click="newDeck" />
<Card id="_add_deck" @click="addDeck" />
</section>
<teleport to="#popup > .popup-content">
<div class="deck new-deck-form-wrapper">
<header>Create a new deck of cards</header>
<DeckForm :deck="newDeck" @save="saveDeck" @close="hidePopup" />
<footer class="centered">
You can also
<button @click="importDeck">import</button>
an existing deck.
</footer>
</div>
</teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { defineComponent, ref, computed } from 'vue'
import { useState } from '@/state'
import Card from '@/components/Card.vue'
import DeckCard from '@/components/DeckCard.vue'
import DeckForm from '@/components/DeckForm.vue'
export default defineComponent({
name: 'Home',
components: { Card },
components: { Card, DeckCard, DeckForm },
setup () {
const { actions: popupActions } = useState('popup')
const { collection: decks, actions: deckActions } = useState('decks')
const newDeckIndex = ref(0)
const newDeck = computed(() => decks.value[newDeckIndex.value])
const addDeck = () => {
const idx = deckActions.new()
newDeckIndex.value = idx
popupActions.show()
}
const saveDeck = (updatedDeck) => {
console.log('saving deck', updatedDeck)
updatedDeck.id = newDeckIndex.value
deckActions.update(updatedDeck)
popupActions.hide()
}
return {
decks,
// TODO: open popup with Deck settings after creation
newDeck: deckActions.new
addDeck,
newDeck,
saveDeck,
hidePopup: popupActions.hide
// importDeck: deckActions.import,
}
}
})