WIP on comp API (#37)

approaching the end of this task
This commit is contained in:
Daniel J. Summers 2019-11-27 20:55:51 -06:00
parent 321ba83ab0
commit 9d44c90de6
16 changed files with 532 additions and 347 deletions

View File

@ -1 +0,0 @@
[{"tagName":"script","closeTag":true,"attributes":{"type":"text/javascript","src":"/js/chunk-vendors-legacy.12c801f9.js"}},{"tagName":"script","closeTag":true,"attributes":{"type":"text/javascript","src":"/js/app-legacy.3c452240.js"}}]

View File

@ -112,9 +112,7 @@ const ProgressSymbol = Symbol('Progress events')
export default createComponent({ export default createComponent({
name: 'app', name: 'app',
components: { components: { Navigation },
Navigation
},
setup () { setup () {
const pkg = require('../package.json') const pkg = require('../package.json')

View File

@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
import { computed, createElement, onBeforeUnmount, onMounted, ref } from '@vue/composition-api' import { computed, createComponent, createElement, onBeforeUnmount, onMounted, ref } from '@vue/composition-api'
import moment from 'moment' import moment from 'moment'
export default { export default createComponent({
name: 'date-from-now',
props: { props: {
tag: { tag: {
type: String, type: String,
@ -18,7 +17,7 @@ export default {
default: 10000 default: 10000
} }
}, },
setup (props: any) { setup (props) {
/** Interval ID for updating relative time */ /** Interval ID for updating relative time */
let intervalId: number = 0 let intervalId: number = 0
@ -47,5 +46,5 @@ export default {
} }
}) })
} }
} })
</script> </script>

View File

@ -27,7 +27,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed } from '@vue/composition-api' import { computed, createComponent } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import { AppState } from '../../store/types' // eslint-disable-line no-unused-vars import { AppState } from '../../store/types' // eslint-disable-line no-unused-vars
@ -36,7 +36,7 @@ import { useAuth } from '../../plugins/auth'
import { useRouter } from '../../plugins/router' import { useRouter } from '../../plugins/router'
import { useStore } from '../../plugins/store' import { useStore } from '../../plugins/store'
export default { export default createComponent({
setup () { setup () {
/** The Vuex store */ /** The Vuex store */
const store = useStore() as Store<AppState> const store = useStore() as Store<AppState>
@ -72,5 +72,5 @@ export default {
showHelp showHelp
} }
} }
} })
</script> </script>

View File

@ -4,9 +4,9 @@ h1(v-if='!hideOnPage'
</template> </template>
<script lang="ts"> <script lang="ts">
import { watch } from '@vue/composition-api' import { createComponent, watch, ref } from '@vue/composition-api'
export default { export default createComponent({
props: { props: {
title: { title: {
type: String, type: String,
@ -17,11 +17,11 @@ export default {
default: false default: false
} }
}, },
setup (props: any) { setup (props) {
watch(props.title, (title, prevTitle) => { watch(ref(props.title), (title: string, prevTitle: string) => {
document.title = `${props.title.replace('&rsquo;', "'")} « myPrayerJournal` document.title = `${props.title.replace('&rsquo;', "'")} « myPrayerJournal`
}) })
return { } return { }
} }
} })
</script> </script>

View File

@ -14,7 +14,7 @@ md-content(role='main').mpj-main-content
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref, onMounted } from '@vue/composition-api' import { createComponent, onMounted, ref } from '@vue/composition-api'
import RequestList from './RequestList.vue' import RequestList from './RequestList.vue'
@ -22,7 +22,7 @@ import api from '../../api'
import { JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars import { JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
import { useProgress, useSnackbar } from '../../App.vue' import { useProgress, useSnackbar } from '../../App.vue'
export default { export default createComponent({
components: { components: {
RequestList RequestList
}, },
@ -59,5 +59,5 @@ export default {
isLoaded isLoaded
} }
} }
} })
</script> </script>

View File

@ -1,6 +1,6 @@
<template lang="pug"> <template lang="pug">
md-content(role='main').mpj-narrow md-content(role='main').mpj-narrow
page-title(:title='title') page-title(:title='title.value')
md-field md-field
label(for='request_text') Prayer Request label(for='request_text') Prayer Request
md-textarea(v-model='form.requestText' md-textarea(v-model='form.requestText'
@ -8,7 +8,7 @@ md-content(role='main').mpj-narrow
md-autogrow md-autogrow
autofocus).mpj-full-width autofocus).mpj-full-width
br br
template(v-if='!isNew') template(v-if='!isNew.value')
label Also Mark As label Also Mark As
br br
md-radio(v-model='form.status' md-radio(v-model='form.status'
@ -34,141 +34,191 @@ md-content(role='main').mpj-narrow
label Count label Count
md-input(v-model='form.recur.count' md-input(v-model='form.recur.count'
type='number' type='number'
:disabled='!showRecurrence') :disabled='!showRecurrence.value')
.md-layout-item.md-size-20 .md-layout-item.md-size-20
md-field md-field
label Interval label Interval
md-select(v-model='form.recur.other' md-select(v-model='form.recur.other'
:disabled='!showRecurrence') :disabled='!showRecurrence.value')
md-option(value='Hours') hours md-option(value='Hours') hours
md-option(value='Days') days md-option(value='Days') days
md-option(value='Weeks') weeks md-option(value='Weeks') weeks
.mpj-text-right .mpj-text-right
md-button(:disabled='!isValidRecurrence' md-button(:disabled='!isValidRecurrence.value'
@click.stop='saveRequest()').md-primary.md-raised #[md-icon save] Save @click.stop='saveRequest()').md-primary.md-raised #[md-icon save] Save
md-button(@click.stop='goBack()').md-raised #[md-icon arrow_back] Cancel md-button(@click.stop='goBack()').md-raised #[md-icon arrow_back] Cancel
</template> </template>
<script> <script lang="ts">
'use strict' import { createComponent, ref, computed, onMounted } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import { mapState } from 'vuex' import { Actions, AppState, AddRequestAction, UpdateRequestAction } from '../../store/types' // eslint-disable-line no-unused-vars
import { useProgress, useSnackbar } from '../../App.vue'
import { useStore } from '../../plugins/store'
import { useRouter } from '../../plugins/router'
import { Actions } from '@/store/types' /** The recurrence settings for the request */
class RecurrenceForm {
/** The type of recurrence */
typ = 'Immediate'
export default { /** The type of recurrence (other than immediate) */
name: 'edit-request', other = ''
inject: [
'messages', /** The count of non-immediate intervals */
'progress' count = ''
],
/**
* The recurrence represented by the given form
* @param x The recurrence form
*/
static recurrence = (x: RecurrenceForm) => x.typ === 'Immediate' ? 'Immediate' : x.other
/**
* The interval represented by the given form
* @param x The recurrence form
*/
static interval = (x: RecurrenceForm) => x.typ === 'Immediate' ? 0 : Number.parseInt(x.count)
}
/** The form for editing the request */
class EditForm {
/** The ID of the request */
requestId = ''
/** The text of the request */
requestText = ''
/** The status associated with this update */
status = 'Updated'
/** The recurrence for the request */
recur = new RecurrenceForm()
}
export default createComponent({
props: { props: {
id: { id: {
type: String, type: String,
required: true required: true
} }
}, },
data () { setup (props) {
return { /** The Vuex store */
title: 'Edit Prayer Request', const store = useStore() as Store<AppState>
isNew: false,
form: { /** The snackbar component properties */
requestId: '', const snackbar = useSnackbar()
requestText: '',
status: 'Updated', /** The progress bar component properties */
recur: { const progress = useProgress()
typ: 'Immediate',
other: '', /** The application router */
count: '' const router = useRouter()
}
} /** The page title */
} const title = ref('Edit Prayer Request')
},
computed: { /** Whether this is a new request */
isValidRecurrence () { const isNew = ref(false)
if (this.form.recur.typ === 'Immediate') return true
const count = Number.parseInt(this.form.recur.count) /** The input form */
if (isNaN(count) || this.form.recur.other === '') return false const form = new EditForm()
if (this.form.recur.other === 'Hours' && count > (365 * 24)) return false
if (this.form.recur.other === 'Days' && count > 365) return false /** Is the selected recurrence a valid recurrence? */
if (this.form.recur.other === 'Weeks' && count > 52) return false const isValidRecurrence = computed(() => {
if (form.recur.typ === 'Immediate') return true
const count = Number.parseInt(form.recur.count)
if (isNaN(count) || form.recur.other === '') return false
if (form.recur.other === 'Hours' && count > (365 * 24)) return false
if (form.recur.other === 'Days' && count > 365) return false
if (form.recur.other === 'Weeks' && count > 52) return false
return true return true
}, })
showRecurrence () {
return this.form.recur.typ !== 'Immediate' /** Whether the recurrence should be shown */
}, const showRecurrence = computed(() => form.recur.typ !== 'Immediate')
...mapState(['journal'])
}, /** Go back 1 in browser history */
async mounted () { const goBack = () => { router.go(-1) }
await this.ensureJournal()
if (this.id === 'new') { /** Trim the request text */
this.title = 'Add Prayer Request' const trimText = () => { form.requestText = form.requestText.trim() }
this.isNew = true
this.form.requestId = '' /** Save the edited request */
this.form.requestText = '' const saveRequest = async () => {
this.form.status = 'Created' if (isNew.value) {
this.form.recur.typ = 'Immediate' const opts: AddRequestAction = {
this.form.recur.other = '' progress,
this.form.recur.count = '' requestText: form.requestText,
} else { recurType: RecurrenceForm.recurrence(form.recur),
this.title = 'Edit Prayer Request' recurCount: RecurrenceForm.interval(form.recur)
this.isNew = false }
if (this.journal.length === 0) { await store.dispatch(Actions.AddRequest, opts)
await this.$store.dispatch(Actions.LoadJournal, this.progress) snackbar.events.$emit('info', 'New prayer request added')
}
const req = this.journal.filter(r => r.requestId === this.id)[0]
this.form.requestId = this.id
this.form.requestText = req.text
this.form.status = 'Updated'
if (req.recurType === 'Immediate') {
this.form.recur.typ = 'Immediate'
this.form.recur.other = ''
this.form.recur.count = ''
} else { } else {
this.form.recur.typ = 'other' const opts: UpdateRequestAction = {
this.form.recur.other = req.recurType progress,
this.form.recur.count = req.recurCount requestId: form.requestId,
} updateText: form.requestText,
} status: form.status,
}, recurType: RecurrenceForm.recurrence(form.recur),
methods: { recurCount: RecurrenceForm.interval(form.recur)
goBack () { }
this.$router.go(-1) await store.dispatch(Actions.UpdateRequest, opts)
}, if (form.status === 'Answered') {
trimText () { snackbar.events.$emit('info', 'Request updated and removed from active journal')
this.form.requestText = this.form.requestText.trim()
},
async ensureJournal () {
if (!Array.isArray(this.journal)) {
await this.$store.dispatch(Actions.LoadJournal, this.progress)
}
},
async saveRequest () {
if (this.isNew) {
await this.$store.dispatch(Actions.AddRequest, {
progress: this.progress,
requestText: this.form.requestText,
recurType: this.form.recur.typ === 'Immediate' ? 'Immediate' : this.form.recur.other,
recurCount: this.form.recur.typ === 'Immediate' ? 0 : Number.parseInt(this.form.recur.count)
})
this.messages.$emit('info', 'New prayer request added')
} else {
await this.$store.dispatch(Actions.UpdateRequest, {
progress: this.progress,
requestId: this.form.requestId,
updateText: this.form.requestText,
status: this.form.status,
recurType: this.form.recur.typ === 'Immediate' ? 'Immediate' : this.form.recur.other,
recurCount: this.form.recur.typ === 'Immediate' ? 0 : Number.parseInt(this.form.recur.count)
})
if (this.form.status === 'Answered') {
this.messages.$emit('info', 'Request updated and removed from active journal')
} else { } else {
this.messages.$emit('info', 'Request updated') snackbar.events.$emit('info', 'Request updated')
} }
} }
this.goBack() goBack()
}
onMounted(async () => {
if (!Array.isArray(store.state.journal)) {
await store.dispatch(Actions.LoadJournal, progress)
}
if (props.id === 'new') {
title.value = 'Add Prayer Request'
isNew.value = true
form.requestId = ''
form.requestText = ''
form.status = 'Created'
form.recur.typ = 'Immediate'
form.recur.other = ''
form.recur.count = ''
} else {
title.value = 'Edit Prayer Request'
isNew.value = false
const req = store.state.journal.filter(r => r.requestId === props.id)[0]
form.requestId = props.id
form.requestText = req.text
form.status = 'Updated'
if (req.recurType === 'Immediate') {
form.recur.typ = 'Immediate'
form.recur.other = ''
form.recur.count = ''
} else {
form.recur.typ = 'other'
form.recur.other = req.recurType
form.recur.count = req.recurCount.toString()
}
}
})
return {
form,
goBack,
isNew,
isValidRecurrence,
journal: store.state.journal,
saveRequest,
showRecurrence,
title,
trimText
} }
} }
} })
</script> </script>

View File

@ -23,7 +23,7 @@ md-content(role='main').mpj-main-content
</template> </template>
<script lang="ts"> <script lang="ts">
import { createComponent, onMounted, computed } from '@vue/composition-api' import { computed, createComponent, onMounted } from '@vue/composition-api'
import moment from 'moment' import moment from 'moment'
import api from '../../api' import api from '../../api'
@ -45,13 +45,16 @@ export default createComponent({
const progress = useProgress() const progress = useProgress()
/** The request being displayed */ /** The request being displayed */
let request: JournalRequest = undefined let request = new JournalRequest()
/** The history entry where the request was marked as answered */
const answer = computed(() => request.history.find(hist => hist.status === 'Answered'))
/** Whether this request is answered */ /** Whether this request is answered */
const isAnswered = computed(() => request.history.find(hist => hist.status === 'Answered')) const isAnswered = computed(() => answer.value !== undefined)
/** The date/time this request was answered */ /** The date/time this request was answered */
const answered = computed(() => request.history.find(hist => hist.status === 'Answered').asOf) const answered = computed(() => answer.value ? answer.value.asOf : undefined)
/** The last recorded text for the request */ /** The last recorded text for the request */
const lastText = computed(() => request.history.filter(hist => hist.text).sort(asOfDesc)[0].text) const lastText = computed(() => request.history.filter(hist => hist.text).sort(asOfDesc)[0].text)
@ -68,8 +71,9 @@ export default createComponent({
/** The number of days this request [was|has been] open */ /** The number of days this request [was|has been] open */
const openDays = computed(() => { const openDays = computed(() => {
const asOf = isAnswered.value ? answered.value : Date.now() const asOf = answered.value ? answered.value : Date.now()
return Math.floor((asOf - request.history.find(hist => hist.status === 'Created').asOf) / 1000 / 60 / 60 / 24) return Math.floor(
(asOf - request.history.filter(hist => hist.status === 'Created')[0].asOf) / 1000 / 60 / 60 / 24)
}) })
/** How many times this request has been prayed for */ /** How many times this request has been prayed for */
@ -82,7 +86,7 @@ export default createComponent({
progress.events.$emit('show', 'indeterminate') progress.events.$emit('show', 'indeterminate')
try { try {
const req = await api.getFullRequest(props.id) const req = await api.getFullRequest(props.id)
request = req.data request = req.data as JournalRequest
} catch (e) { } catch (e) {
console.log(e) // eslint-disable-line no-console console.log(e) // eslint-disable-line no-console
} finally { } finally {

View File

@ -11,7 +11,7 @@ md-dialog(:md-active.sync='notesVisible').mpj-note-dialog
md-button(@click='saveNotes()').md-primary #[md-icon save] Save md-button(@click='saveNotes()').md-primary #[md-icon save] Save
md-button(@click='closeDialog()') #[md-icon undo] Cancel md-button(@click='closeDialog()') #[md-icon undo] Cancel
md-dialog-content(md-scrollbar='true').mpj-dialog-content md-dialog-content(md-scrollbar='true').mpj-dialog-content
div(v-if='hasPriorNotes') div(v-if='hasPriorNotes.value')
p.mpj-text-center: strong Prior Notes for This Request p.mpj-text-center: strong Prior Notes for This Request
.mpj-note-list .mpj-note-list
p(v-for='note in priorNotes' p(v-for='note in priorNotes'
@ -19,88 +19,125 @@ md-dialog(:md-active.sync='notesVisible').mpj-note-dialog
small.mpj-muted-text: date-from-now(:value='note.asOf') small.mpj-muted-text: date-from-now(:value='note.asOf')
br br
span.mpj-request-text {{ note.notes }} span.mpj-request-text {{ note.notes }}
div(v-else-if='noPriorNotes').mpj-text-center.mpj-muted-text There are no prior notes for this request div(v-else-if='noPriorNotes.value').mpj-text-center.mpj-muted-text There are no prior notes for this request
div(v-else).mpj-text-center div(v-else).mpj-text-center
hr hr
md-button(@click='loadNotes()') #[md-icon cloud_download] Load Prior Notes md-button(@click='loadNotes()') #[md-icon cloud_download] Load Prior Notes
</template> </template>
<script> <script lang="ts">
'use strict' import { computed, createComponent, ref } from '@vue/composition-api'
import api from '@/api' import api from '../../api'
export default { import { useEvents } from '../Journal.vue'
name: 'notes-edit', import { NotesEntry, JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
inject: [ import { useProgress, useSnackbar } from '../../App.vue'
'journalEvents',
'messages', /** The input form for the notes dialog */
'progress' class NotesForm {
], /** The ID of the request */
data () { requestId = ''
return { /** The actual notes */
notesVisible: false, notes = ''
form: { }
requestId: '',
notes: '' /** The prior notes for this request */
}, class PriorNotes {
priorNotes: [], /** The prior notes */
priorNotesLoaded: false notes: NotesEntry[] = []
/** Have the prior notes been loaded? */
isLoaded = false
}
export default createComponent({
setup () {
/** The event bus for the journal page */
const events = useEvents()
/** The snackbar component properties */
const snackbar = useSnackbar()
/** The progress bar component properties */
const progress = useProgress()
/** Is this dialog visible? */
const notesVisible = ref(false)
/** The input form */
const form = new NotesForm()
/** The prior notes */
const prior = new PriorNotes()
/** Are there prior notes? */
const hasPriorNotes = computed(() => prior.isLoaded && prior.notes.length > 0)
/** Are there no prior notes? */
const noPriorNotes = computed(() => prior.isLoaded && prior.notes.length === 0)
/** Close this dialog */
const closeDialog = () => {
form.requestId = ''
form.notes = ''
prior.notes = []
prior.isLoaded = false
notesVisible.value = false
} }
},
computed: { /** Load the notes for this request */
hasPriorNotes () { const loadNotes = async () => {
return this.priorNotesLoaded && this.priorNotes.length > 0 progress.events.$emit('show', 'indeterminate')
},
noPriorNotes () {
return this.priorNotesLoaded && this.priorNotes.length === 0
}
},
created () {
this.journalEvents.$on('notes', this.openDialog)
},
methods: {
closeDialog () {
this.form.requestId = ''
this.form.notes = ''
this.priorNotes = []
this.priorNotesLoaded = false
this.notesVisible = false
},
async loadNotes () {
this.progress.$emit('show', 'indeterminate')
try { try {
const notes = await api.getNotes(this.form.requestId) const notes = await api.getNotes(form.requestId)
this.priorNotes = notes.data.sort((a, b) => b.asOf - a.asOf) prior.notes = (notes.data as NotesEntry[]).sort((a, b) => b.asOf - a.asOf)
this.progress.$emit('done')
} catch (e) { } catch (e) {
console.error(e) // eslint-disable-line no-console console.error(e) // eslint-disable-line no-console
this.progress.$emit('done')
} finally { } finally {
this.priorNotesLoaded = true progress.events.$emit('done')
prior.isLoaded = true
} }
}, }
openDialog (request) {
this.form.requestId = request.requestId /** Open this dialog */
this.notesVisible = true const openDialog = (request: JournalRequest) => {
}, form.requestId = request.requestId
async saveNotes () { notesVisible.value = true
this.progress.$emit('show', 'indeterminate') }
/** Save the notes entered on this dialog */
const saveNotes = async () => {
progress.events.$emit('show', 'indeterminate')
try { try {
await api.addNote(this.form.requestId, this.form.notes) await api.addNote(form.requestId, form.notes)
this.progress.$emit('done') snackbar.events.$emit('info', 'Added notes')
this.messages.$emit('info', 'Added notes') closeDialog()
this.closeDialog()
} catch (e) { } catch (e) {
console.error(e) // eslint-disable-line no-console console.error(e) // eslint-disable-line no-console
this.progress.$emit('done') } finally {
progress.events.$emit('done')
} }
}, }
trimText () {
this.form.notes = this.form.notes.trim() /** Trim the note text */
const trimText = () => { form.notes = form.notes.trim() }
events.$on('notes', openDialog)
return {
closeDialog,
form,
hasPriorNotes,
loadNotes,
noPriorNotes,
notesVisible,
openDialog,
prior,
saveNotes,
trimText
} }
} }
} })
</script> </script>
<style lang="sass"> <style lang="sass">

View File

@ -24,48 +24,78 @@ md-card(v-if='shouldDisplay'
p.mpj-text-right: small.mpj-muted-text: em (last activity #[date-from-now(:value='request.asOf')]) p.mpj-text-right: small.mpj-muted-text: em (last activity #[date-from-now(:value='request.asOf')])
</template> </template>
<script> <script lang="ts">
'use strict' import { createComponent, computed } from '@vue/composition-api'
import { Actions } from '@/store/types' import { Actions, JournalRequest, UpdateRequestAction } from '../../store/types' // eslint-disable-line no-unused-vars
export default { import { useEvents } from '../Journal.vue'
name: 'request-card', import { useStore } from '../../plugins/store'
inject: [ import { useProgress, useSnackbar } from '../../App.vue'
'journalEvents', import { useRouter } from '../../plugins/router'
'messages',
'progress' export default createComponent({
],
props: { props: {
request: { required: true } request: {
}, type: JournalRequest,
computed: { required: true
shouldDisplay () {
const now = Date.now()
return Math.max(now, this.request.showAfter, this.request.snoozedUntil) === now
} }
}, },
methods: { setup (props) {
async markPrayed () { /** The Vuex store */
await this.$store.dispatch(Actions.UpdateRequest, { const store = useStore()
progress: this.progress,
requestId: this.request.requestId, /** The application router */
const router = useRouter()
/** The progress bar component properties */
const progress = useProgress()
/** The snackbar component properties */
const snackbar = useSnackbar()
/** The journal event bus */
const events = useEvents()
/** Should this request be displayed? */
const shouldDisplay = computed(() => {
const now = Date.now()
return Math.max(now, props.request.showAfter, props.request.snoozedUntil) === now
})
/** Mark the request as prayed */
const markPrayed = async () => {
const opts: UpdateRequestAction = {
progress,
requestId: props.request.requestId,
status: 'Prayed', status: 'Prayed',
updateText: '' updateText: '',
}) recurType: '',
this.messages.$emit('info', 'Request marked as prayed') recurCount: 0
}, }
showEdit () { await store.dispatch(Actions.UpdateRequest, opts)
this.$router.push({ name: 'EditRequest', params: { id: this.request.requestId } }) snackbar.events.$emit('info', 'Request marked as prayed')
}, }
showNotes () {
this.journalEvents.$emit('notes', this.request) /** Show the edit page for this request */
}, const showEdit = () => { router.push({ name: 'EditRequest', params: { id: props.request.requestId } }) }
snooze () {
this.journalEvents.$emit('snooze', this.request.requestId) /** Show the request notes dialog */
const showNotes = () => events.$emit('notes', props.request)
/** Show the snooze request dialog */
const snooze = () => events.$emit('snooze', props.request.requestId)
return {
markPrayed,
request: props.request,
shouldDisplay,
showEdit,
showNotes,
snooze
} }
} }
} })
</script> </script>
<style lang="sass"> <style lang="sass">

View File

@ -77,9 +77,7 @@ export default createComponent({
} }
await store.dispatch(Actions.SnoozeRequest, opts) await store.dispatch(Actions.SnoozeRequest, opts)
snackbar.events.$emit('info', 'Request un-snoozed') snackbar.events.$emit('info', 'Request un-snoozed')
if (parent) { if (parent) parent.$emit('requestUnsnoozed')
parent.$emit('requestUnsnoozed')
}
} }
/** Edit the given request */ /** Edit the given request */
@ -94,9 +92,7 @@ export default createComponent({
} }
await store.dispatch(Actions.ShowRequestNow, opts) await store.dispatch(Actions.ShowRequestNow, opts)
snackbar.events.$emit('info', 'Recurrence skipped; request now shows in journal') snackbar.events.$emit('info', 'Recurrence skipped; request now shows in journal')
if (parent) { if (parent) parent.$emit('requestNowShown')
parent.$emit('requestNowShown')
}
} }
/** View the full request */ /** View the full request */

View File

@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
md-dialog(:md-active.sync='snoozeVisible').mpj-skinny md-dialog(:md-active.sync='isVisible.value').mpj-skinny
md-dialog-title Snooze Prayer Request md-dialog-title Snooze Prayer Request
md-content.mpj-dialog-content md-content.mpj-dialog-content
span.mpj-text-muted Until span.mpj-text-muted Until
@ -7,63 +7,90 @@ md-dialog(:md-active.sync='snoozeVisible').mpj-skinny
:md-disabled-dates='datesInPast' :md-disabled-dates='datesInPast'
md-immediately) md-immediately)
md-dialog-actions md-dialog-actions
md-button(:disabled='!isValid' md-button(:disabled='!isValid.value'
@click='snoozeRequest()').md-primary #[md-icon snooze] Snooze @click='snoozeRequest()').md-primary #[md-icon snooze] Snooze
md-button(@click='closeDialog()') #[md-icon undo] Cancel md-button(@click='closeDialog()') #[md-icon undo] Cancel
</template> </template>
<script> <script lang="ts">
'use strict' import { createComponent, ref, computed } from '@vue/composition-api'
import { Actions } from '@/store/types' import { Actions, SnoozeRequestAction } from '../../store/types' // eslint-disable-line no-unused-vars
export default { import { useProgress, useSnackbar } from '../../App.vue'
name: 'snooze-request', import { useEvents } from '../Journal.vue'
inject: [ import { useStore } from '../../plugins/store'
'journalEvents',
'messages', /** The input form */
'progress' class SnoozeForm {
], /** The ID of the request */
props: { requestId = ''
events: { required: true }
}, /** The date until which the request will be snoozed */
data () { snoozedUntil = ''
return { }
snoozeVisible: false,
datesInPast: date => date < new Date(), export default createComponent({
form: { setup () {
requestId: '', /** The Vuex store */
snoozedUntil: '' const store = useStore()
/** The progress bar component properties */
const progress = useProgress()
/** The snackbar component properties */
const snackbar = useSnackbar()
/** The journal event bus */
const events = useEvents()
/** Whether this dialog is visible */
const isVisible = ref(false)
/** The input form */
const form = new SnoozeForm()
/** Is the input date valid? */
const isValid = computed(() => !isNaN(Date.parse(form.snoozedUntil)))
/** Close the dialog */
const closeDialog = () => {
form.requestId = ''
form.snoozedUntil = ''
isVisible.value = false
}
/**
* Open the dialog
* @param requestId The ID of the request to be snoozed
*/
const openDialog = (requestId: string) => {
form.requestId = requestId
isVisible.value = true
}
const snoozeRequest = async () => {
const opts: SnoozeRequestAction = {
progress,
requestId: form.requestId,
until: Date.parse(form.snoozedUntil)
} }
await store.dispatch(Actions.SnoozeRequest, opts)
snackbar.events.$emit('info', `Request snoozed until ${form.snoozedUntil}`)
closeDialog()
} }
},
created () { events.$on('snooze', openDialog)
this.journalEvents.$on('snooze', this.openDialog)
}, return {
computed: { closeDialog,
isValid () { datesInPast: (date: Date) => date < new Date(),
return !isNaN(Date.parse(this.form.snoozedUntil)) form,
} isValid,
}, isVisible,
methods: { openDialog,
closeDialog () { snoozeRequest
this.form.requestId = ''
this.form.snoozedUntil = ''
this.snoozeVisible = false
},
openDialog (requestId) {
this.form.requestId = requestId
this.snoozeVisible = true
},
async snoozeRequest () {
await this.$store.dispatch(Actions.SnoozeRequest, {
progress: this.progress,
requestId: this.form.requestId,
until: Date.parse(this.form.snoozedUntil)
})
this.messages.$emit('info', `Request snoozed until ${this.form.snoozedUntil}`)
this.closeDialog()
} }
} }
} })
</script> </script>

View File

@ -2,7 +2,7 @@
article.mpj-main-content(role='main') article.mpj-main-content(role='main')
page-title(title='Snoozed Requests' page-title(title='Snoozed Requests'
hide-on-page=true) hide-on-page=true)
template(v-if='loaded') template(v-if='isLoaded.value')
md-empty-state(v-if='requests.length === 0' md-empty-state(v-if='requests.length === 0'
md-icon='sentiment_dissatisfied' md-icon='sentiment_dissatisfied'
md-label='No Snoozed Requests' md-label='No Snoozed Requests'
@ -14,47 +14,51 @@ article.mpj-main-content(role='main')
p(v-else) Loading journal... p(v-else) Loading journal...
</template> </template>
<script> <script lang="ts">
'use strict' import { createComponent, ref, onMounted } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import { mapState } from 'vuex' import RequestList from './RequestList.vue'
import { Actions } from '@/store/types' import { Actions, AppState, JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
import { useStore } from '../../plugins/store'
import { useProgress } from '../../App.vue'
import RequestList from '@/components/request/RequestList' export default createComponent({
components: { RequestList },
setup () {
/** The Vuex store */
const store = useStore() as Store<AppState>
export default { /** The progress bar component properties */
name: 'snoozed-requests', const progress = useProgress()
inject: ['progress'],
components: { /** The snoozed requests */
RequestList let requests: JournalRequest[] = []
},
data () { /** Have snoozed requests been loaded? */
return { const isLoaded = ref(false)
requests: [],
loaded: false /** Ensure the latest journal is loaded, and filter it to snoozed requests */
} const ensureJournal = async () => {
}, if (!Array.isArray(store.state.journal)) {
computed: { isLoaded.value = false
...mapState(['journal', 'isLoadingJournal']) await store.dispatch(Actions.LoadJournal, progress)
},
created () {
this.$on('requestUnsnoozed', this.ensureJournal)
},
methods: {
async ensureJournal () {
if (!Array.isArray(this.journal)) {
this.loaded = false
await this.$store.dispatch(Actions.LoadJournal, this.progress)
} }
this.requests = this.journal requests = store.state.journal
.filter(req => req.snoozedUntil > Date.now()) .filter(req => req.snoozedUntil > Date.now())
.sort((a, b) => a.snoozedUntil - b.snoozedUntil) .sort((a, b) => a.snoozedUntil - b.snoozedUntil)
this.loaded = true isLoaded.value = true
}
onMounted(ensureJournal)
// this.$on('requestUnsnoozed', ensureJournal)
return {
requests,
isLoaded
} }
},
async mounted () {
await this.ensureJournal()
} }
} })
</script> </script>

View File

@ -5,17 +5,18 @@ article.mpj-main-content(role='main')
</template> </template>
<script lang="ts"> <script lang="ts">
import { onBeforeMount } from '@vue/composition-api' import { createComponent, onBeforeMount } from '@vue/composition-api'
import VueRouter from 'vue-router' // eslint-disable-line no-unused-vars import VueRouter from 'vue-router' // eslint-disable-line no-unused-vars
import { Store } from 'vuex' // eslint-disable-line no-unused-vars import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import { AppState } from '../../store/types' // eslint-disable-line no-unused-vars import { AppState } from '../../store/types' // eslint-disable-line no-unused-vars
import { AuthService } from '../../auth' // eslint-disable-line no-unused-vars import { AuthService } from '../../auth' // eslint-disable-line no-unused-vars
import { useAuth } from '../../plugins/auth' import { useAuth } from '../../plugins/auth'
import { useRouter } from '../../plugins/router' import { useRouter } from '../../plugins/router'
import { useStore } from '../../plugins/store' import { useStore } from '../../plugins/store'
export default { export default createComponent({
setup () { setup () {
/** Auth service instance */ /** Auth service instance */
const auth = useAuth() as AuthService const auth = useAuth() as AuthService
@ -36,5 +37,5 @@ export default {
return { } return { }
} }
} })
</script> </script>

View File

@ -10,7 +10,9 @@ import {
JournalRequest, JournalRequest,
Mutations, Mutations,
SnoozeRequestAction, SnoozeRequestAction,
ShowRequestAction ShowRequestAction,
AddRequestAction,
UpdateRequestAction
} from './types' } from './types'
import { ProgressProps } from '@/types' import { ProgressProps } from '@/types'
@ -101,16 +103,17 @@ const store : StoreOptions<AppState> = {
} }
}, },
actions: { actions: {
async [Actions.AddRequest] ({ commit }, { progress, requestText, recurType, recurCount }) { async [Actions.AddRequest] ({ commit }, p: AddRequestAction) {
progress.$emit('show', 'indeterminate') const { progress, requestText, recurType, recurCount } = p
progress.events.$emit('show', 'indeterminate')
try { try {
await setBearer() await setBearer()
const newRequest = await api.addRequest(requestText, recurType, recurCount) const newRequest = await api.addRequest(requestText, recurType, recurCount)
commit(Mutations.RequestAdded, newRequest.data) commit(Mutations.RequestAdded, newRequest.data)
progress.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
progress.$emit('done') } finally {
progress.events.$emit('done')
} }
}, },
async [Actions.CheckAuthentication] ({ commit }) { async [Actions.CheckAuthentication] ({ commit }) {
@ -129,19 +132,19 @@ const store : StoreOptions<AppState> = {
try { try {
const jrnl = await api.journal() const jrnl = await api.journal()
commit(Mutations.LoadedJournal, jrnl.data) commit(Mutations.LoadedJournal, jrnl.data)
progress.events.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
progress.events.$emit('done')
} finally { } finally {
progress.events.$emit('done')
commit(Mutations.LoadingJournal, false) commit(Mutations.LoadingJournal, false)
} }
}, },
async [Actions.UpdateRequest] ({ commit, state }, { progress, requestId, status, updateText, recurType, recurCount }) { async [Actions.UpdateRequest] ({ commit, state }, p: UpdateRequestAction) {
progress.$emit('show', 'indeterminate') const { progress, requestId, status, updateText, recurType, recurCount } = p
progress.events.$emit('show', 'indeterminate')
try { try {
await setBearer() await setBearer()
const oldReq: any = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {} const oldReq = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {}
if (!(status === 'Prayed' && updateText === '')) { if (!(status === 'Prayed' && updateText === '')) {
if (status !== 'Answered' && (oldReq.recurType !== recurType || oldReq.recurCount !== recurCount)) { if (status !== 'Answered' && (oldReq.recurType !== recurType || oldReq.recurCount !== recurCount)) {
await api.updateRecurrence(requestId, recurType, recurCount) await api.updateRecurrence(requestId, recurType, recurCount)
@ -152,10 +155,10 @@ const store : StoreOptions<AppState> = {
} }
const request = await api.getRequest(requestId) const request = await api.getRequest(requestId)
commit(Mutations.RequestUpdated, request.data) commit(Mutations.RequestUpdated, request.data)
progress.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
progress.$emit('done') } finally {
progress.events.$emit('done')
} }
}, },
async [Actions.ShowRequestNow] ({ commit }, p: ShowRequestAction) { async [Actions.ShowRequestNow] ({ commit }, p: ShowRequestAction) {
@ -166,9 +169,9 @@ const store : StoreOptions<AppState> = {
await api.showRequest(requestId, showAfter) await api.showRequest(requestId, showAfter)
const request = await api.getRequest(requestId) const request = await api.getRequest(requestId)
commit(Mutations.RequestUpdated, request.data) commit(Mutations.RequestUpdated, request.data)
progress.events.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
} finally {
progress.events.$emit('done') progress.events.$emit('done')
} }
}, },
@ -180,9 +183,9 @@ const store : StoreOptions<AppState> = {
await api.snoozeRequest(requestId, until) await api.snoozeRequest(requestId, until)
const request = await api.getRequest(requestId) const request = await api.getRequest(requestId)
commit(Mutations.RequestUpdated, request.data) commit(Mutations.RequestUpdated, request.data)
progress.events.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
} finally {
progress.events.$emit('done') progress.events.$emit('done')
} }
} }

View File

@ -12,6 +12,15 @@ export class HistoryEntry {
text?: string = undefined text?: string = undefined
} }
/** An entry with notes for a request */
export class NotesEntry {
/** The date/time the notes were recorded */
asOf = 0
/** The notes */
notes = ''
}
/** A prayer request that is part of the user's journal */ /** A prayer request that is part of the user's journal */
export class JournalRequest { export class JournalRequest {
/** The ID of the request (just the CUID part) */ /** The ID of the request (just the CUID part) */
@ -45,7 +54,7 @@ export class JournalRequest {
history: HistoryEntry[] = [] history: HistoryEntry[] = []
/** Note entries for the request */ /** Note entries for the request */
notes: any[] = [] // Note list notes: NotesEntry[] = []
} }
/** The state of myPrayerJournal */ /** The state of myPrayerJournal */
@ -108,9 +117,21 @@ const mutations = {
} }
export { mutations as Mutations } export { mutations as Mutations }
/** The shape of the parameter to the add request action */
export interface AddRequestAction {
/** The progress bar component properties */
progress: ProgressProps
/** The text of the request */
requestText: string
/** The recurrence type */
recurType: string
/** The number of intervals for non-immediate recurrence */
recurCount: number
}
/** The shape of the parameter to the show request action */ /** The shape of the parameter to the show request action */
export interface ShowRequestAction { export interface ShowRequestAction {
/** The progress bar component instance */ /** The progress bar component properties */
progress: ProgressProps progress: ProgressProps
/** The ID of the prayer request being shown */ /** The ID of the prayer request being shown */
requestId: string requestId: string
@ -120,10 +141,26 @@ export interface ShowRequestAction {
/** The shape of the parameter to the snooze request action */ /** The shape of the parameter to the snooze request action */
export interface SnoozeRequestAction { export interface SnoozeRequestAction {
/** The progress bar component instance */ /** The progress bar component properties */
progress: ProgressProps progress: ProgressProps
/** The ID of the prayer request being snoozed/unsnoozed */ /** The ID of the prayer request being snoozed/unsnoozed */
requestId: string requestId: string
/** The date/time after which the request will be once again shown */ /** The date/time after which the request will be once again shown */
until: number until: number
} }
/** The shape of the parameter to the update request action */
export interface UpdateRequestAction {
/** The progress bar component properties */
progress: ProgressProps
/** The ID of the prayer request */
requestId: string
/** The text of the update */
updateText: string
/** The status associated with the update */
status: string
/** The type of recurrence for the request */
recurType: string
/** The number of intervals for non-immediate recurrence */
recurCount: number
}