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 this.$store.dispatch(Actions.LoadJournal, this.progress)
} }
const req = this.journal.filter(r => r.requestId === this.id)[0] await store.dispatch(Actions.AddRequest, opts)
this.form.requestId = this.id snackbar.events.$emit('info', 'New prayer request added')
this.form.requestText = req.text } else {
this.form.status = 'Updated' const opts: UpdateRequestAction = {
progress,
requestId: form.requestId,
updateText: form.requestText,
status: form.status,
recurType: RecurrenceForm.recurrence(form.recur),
recurCount: RecurrenceForm.interval(form.recur)
}
await store.dispatch(Actions.UpdateRequest, opts)
if (form.status === 'Answered') {
snackbar.events.$emit('info', 'Request updated and removed from active journal')
} else {
snackbar.events.$emit('info', 'Request updated')
}
}
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') { if (req.recurType === 'Immediate') {
this.form.recur.typ = 'Immediate' form.recur.typ = 'Immediate'
this.form.recur.other = '' form.recur.other = ''
this.form.recur.count = '' form.recur.count = ''
} else { } else {
this.form.recur.typ = 'other' form.recur.typ = 'other'
this.form.recur.other = req.recurType form.recur.other = req.recurType
this.form.recur.count = req.recurCount form.recur.count = req.recurCount.toString()
} }
} }
},
methods: {
goBack () {
this.$router.go(-1)
},
trimText () {
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 { return {
await this.$store.dispatch(Actions.UpdateRequest, { form,
progress: this.progress, goBack,
requestId: this.form.requestId, isNew,
updateText: this.form.requestText, isValidRecurrence,
status: this.form.status, journal: store.state.journal,
recurType: this.form.recur.typ === 'Immediate' ? 'Immediate' : this.form.recur.other, saveRequest,
recurCount: this.form.recur.typ === 'Immediate' ? 0 : Number.parseInt(this.form.recur.count) showRecurrence,
title,
trimText
}
}
}) })
if (this.form.status === 'Answered') {
this.messages.$emit('info', 'Request updated and removed from active journal')
} else {
this.messages.$emit('info', 'Request updated')
}
}
this.goBack()
}
}
}
</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: ''
},
priorNotes: [],
priorNotesLoaded: false
} }
},
computed: { /** The prior notes for this request */
hasPriorNotes () { class PriorNotes {
return this.priorNotesLoaded && this.priorNotes.length > 0 /** The prior notes */
}, notes: NotesEntry[] = []
noPriorNotes () { /** Have the prior notes been loaded? */
return this.priorNotesLoaded && this.priorNotes.length === 0 isLoaded = false
} }
},
created () { export default createComponent({
this.journalEvents.$on('notes', this.openDialog) setup () {
}, /** The event bus for the journal page */
methods: { const events = useEvents()
closeDialog () {
this.form.requestId = '' /** The snackbar component properties */
this.form.notes = '' const snackbar = useSnackbar()
this.priorNotes = []
this.priorNotesLoaded = false /** The progress bar component properties */
this.notesVisible = false const progress = useProgress()
},
async loadNotes () { /** Is this dialog visible? */
this.progress.$emit('show', 'indeterminate') 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
}
/** Load the notes for this request */
const loadNotes = async () => {
progress.events.$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,
required: true
}
}, },
computed: { setup (props) {
shouldDisplay () { /** The Vuex store */
const store = useStore()
/** 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() const now = Date.now()
return Math.max(now, this.request.showAfter, this.request.snoozedUntil) === now return Math.max(now, props.request.showAfter, props.request.snoozedUntil) === now
}
},
methods: {
async markPrayed () {
await this.$store.dispatch(Actions.UpdateRequest, {
progress: this.progress,
requestId: this.request.requestId,
status: 'Prayed',
updateText: ''
}) })
this.messages.$emit('info', 'Request marked as prayed')
}, /** Mark the request as prayed */
showEdit () { const markPrayed = async () => {
this.$router.push({ name: 'EditRequest', params: { id: this.request.requestId } }) const opts: UpdateRequestAction = {
}, progress,
showNotes () { requestId: props.request.requestId,
this.journalEvents.$emit('notes', this.request) status: 'Prayed',
}, updateText: '',
snooze () { recurType: '',
this.journalEvents.$emit('snooze', this.request.requestId) recurCount: 0
} }
await store.dispatch(Actions.UpdateRequest, opts)
snackbar.events.$emit('info', 'Request marked as prayed')
}
/** Show the edit page for this request */
const showEdit = () => { router.push({ name: 'EditRequest', params: { id: props.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
import { useProgress, useSnackbar } from '../../App.vue'
import { useEvents } from '../Journal.vue'
import { useStore } from '../../plugins/store'
/** The input form */
class SnoozeForm {
/** The ID of the request */
requestId = ''
/** The date until which the request will be snoozed */
snoozedUntil = ''
}
export default createComponent({
setup () {
/** The Vuex store */
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()
}
events.$on('snooze', openDialog)
export default {
name: 'snooze-request',
inject: [
'journalEvents',
'messages',
'progress'
],
props: {
events: { required: true }
},
data () {
return { return {
snoozeVisible: false, closeDialog,
datesInPast: date => date < new Date(), datesInPast: (date: Date) => date < new Date(),
form: { form,
requestId: '', isValid,
snoozedUntil: '' isVisible,
openDialog,
snoozeRequest
} }
} }
},
created () {
this.journalEvents.$on('snooze', this.openDialog)
},
computed: {
isValid () {
return !isNaN(Date.parse(this.form.snoozedUntil))
}
},
methods: {
closeDialog () {
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)) {
isLoaded.value = false
await store.dispatch(Actions.LoadJournal, progress)
} }
}, requests = store.state.journal
computed: {
...mapState(['journal', 'isLoadingJournal'])
},
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
.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
} }
},
async mounted () { onMounted(ensureJournal)
await this.ensureJournal()
// this.$on('requestUnsnoozed', ensureJournal)
return {
requests,
isLoaded
} }
} }
})
</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
}